001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022
023package org.granite.generator.as3;
024
025import java.io.ByteArrayOutputStream;
026import java.io.File;
027import java.io.IOException;
028import java.io.OutputStream;
029import java.io.OutputStreamWriter;
030import java.io.PrintWriter;
031import java.lang.reflect.ParameterizedType;
032import java.lang.reflect.Type;
033import java.net.URL;
034import java.text.DateFormat;
035import java.util.ArrayList;
036import java.util.Date;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043import org.granite.generator.Generator;
044import org.granite.generator.Input;
045import org.granite.generator.Listener;
046import org.granite.generator.TemplateUri;
047import org.granite.generator.as3.reflect.ClientImport;
048import org.granite.generator.as3.reflect.JavaAbstractType.GenerationType;
049import org.granite.generator.as3.reflect.JavaEnum;
050import org.granite.generator.as3.reflect.JavaFieldProperty;
051import org.granite.generator.as3.reflect.JavaImport;
052import org.granite.generator.as3.reflect.JavaInterface;
053import org.granite.generator.as3.reflect.JavaProperty;
054import org.granite.generator.as3.reflect.JavaRemoteDestination;
055import org.granite.generator.as3.reflect.JavaType;
056import org.granite.generator.as3.reflect.JavaType.Kind;
057import org.granite.generator.as3.reflect.JavaTypeFactory;
058import org.granite.generator.exception.TemplateException;
059import org.granite.generator.exception.TemplateUriException;
060import org.granite.generator.gsp.AbstractGroovyTransformer;
061import org.granite.generator.gsp.GroovyTemplate;
062import org.granite.util.ClassUtil;
063
064/**
065 * @author Franck WOLFF
066 */
067public abstract class JavaClientGroovyTransformer
068        extends AbstractGroovyTransformer<JavaAs3Input, JavaAs3Output, JavaAs3GroovyConfiguration>
069        implements JavaTypeFactory {
070
071        private static final String GENERATED_BASE_SUFFIX = "Base";
072        
073        protected final Map<Class<?>, JavaType> javaTypes = new HashMap<Class<?>, JavaType>();
074    protected final Map<String, JavaImport> javaImports = new HashMap<String, JavaImport>();
075    protected final Map<String, JavaImport> javaPropertyImports = new HashMap<String, JavaImport>();
076
077        public JavaClientGroovyTransformer() {
078        }
079
080        public JavaClientGroovyTransformer(JavaAs3GroovyConfiguration config, Listener listener) {
081                super(config, listener);
082        }
083
084        @Override
085        public boolean accept(Input<?> input) {
086                return (input instanceof JavaAs3Input);
087        }
088
089        @Override
090        protected JavaAs3Output[] getOutputs(JavaAs3Input input) throws IOException, TemplateUriException {
091                JavaType javaType = getJavaType(input.getType());
092                input.setJavaType(javaType);
093                TemplateUri[] templateUris = getTemplateUris(javaType);
094                boolean hasBaseTemplate = templateUris.length > 1;
095                
096                JavaAs3Output[] outputs = new JavaAs3Output[templateUris.length];
097                
098                for (int i = 0; i < templateUris.length; i++) {
099                        GroovyTemplate template = getTemplate(templateUris[i]);
100                        File dir = getOutputDir(input, template);
101                        File file = getOutputFile(input, template, dir);
102                        boolean outdated = isOutdated(input, template, file, hasBaseTemplate);
103                        String status = getOutputStatus(input, template, file, hasBaseTemplate);
104                        
105                        outputs[i] = new JavaAs3Output(
106                                javaType,
107                                template,
108                                dir,
109                                file,
110                                outdated,
111                                status
112                        );
113                }
114                
115                return outputs;
116        }
117
118        @Override
119        protected void generate(JavaAs3Input input, JavaAs3Output output) throws IOException, TemplateException {
120                Map<String, Object> bindings = getBindings(input, output);
121                
122                // Write in memory (step 1).
123                PublicByteArrayOutputStream pbaos = new PublicByteArrayOutputStream(8192);
124                output.getTemplate().execute(bindings, new PrintWriter(new OutputStreamWriter(pbaos, "UTF-8")));
125                
126                // If no exceptions were raised, write to file (step 2).
127                OutputStream stream = null;
128                try {
129                        stream = output.openStream();
130                        stream.write(pbaos.getBytes(), 0, pbaos.size());
131                } finally {
132                        if (stream != null)
133                                stream.close();
134                }
135        }
136
137    protected Map<String, Object> getBindings(JavaAs3Input input, JavaAs3Output output) {
138        Map<String, Object> bindings = new HashMap<String, Object>();
139        bindings.put("gVersion", Generator.VERSION);
140        bindings.put("jClass", input.getJavaType());
141        bindings.put("fAttributes", input.getAttributes());
142        return bindings;
143    }
144
145    protected TemplateUri[] getTemplateUris(JavaType javaType) {
146        return getConfig().getTemplateUris(getKind(javaType.getType()), javaType.getClass());
147    }
148    
149    protected File getOutputDir(JavaAs3Input input, GroovyTemplate template) {
150        return (template.isBase() ? getConfig().getBaseOutputDir(input) : getConfig().getOutputDir(input));
151    }
152
153    protected abstract File getOutputFile(JavaAs3Input input, GroovyTemplate template, File outputDir);
154    
155    protected String getOutputFileSuffix(JavaAs3Input input, GroovyTemplate template) {
156        return template.isBase() ? GENERATED_BASE_SUFFIX : "";
157    }
158    
159    protected boolean isOutdated(JavaAs3Input input, GroovyTemplate template, File outputFile, boolean hasBaseTemplate) {
160        if (!outputFile.exists())
161                return true;
162        if (outputFile.lastModified() > System.currentTimeMillis()) {
163                getListener().warn(
164                                outputFile.getAbsolutePath() +
165                                " has a last modified time in the future: " +
166                                DateFormat.getInstance().format(new Date(outputFile.lastModified()))
167                );
168        }
169        if (!template.isBase() && hasBaseTemplate)
170                return false;
171        return input.getFile().lastModified() > outputFile.lastModified();
172    }
173    
174    protected String getOutputStatus(JavaAs3Input input, GroovyTemplate template, File outputFile, boolean hasBaseTemplate) {
175        if (!outputFile.exists())
176                return Listener.MSG_FILE_NOT_EXISTS;
177        if (!template.isBase() && hasBaseTemplate)
178                return Listener.MSG_FILE_EXISTS_NO_OVER;
179        if (input.getFile().lastModified() > outputFile.lastModified())
180                return Listener.MSG_FILE_OUTDATED;
181        return Listener.MSG_FILE_UPTODATE;
182    }
183
184        @Override
185        public ClientType getClientType(Type type, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) {
186        Class<?> clazz = ClassUtil.classOfType(type);
187        
188        ClientType clientType = getConfig().getAs3TypeFactory().getClientType(type, declaringClass, declaringTypes, propertyType);
189        if (clientType == null)
190                clientType = getConfig().getAs3TypeFactory().getAs3Type(clazz);
191        
192        clientType = clientType.translatePackages(getConfig().getTranslators());
193
194        return clientType;
195        }
196
197        @Override
198        public ClientType getAs3Type(Class<?> clazz) {
199        ClientType clientType = getConfig().getAs3TypeFactory().getAs3Type(clazz);
200        if (getConfig().getTranslators().isEmpty() || clazz.getPackage() == null)
201            return clientType;
202
203        String packageName = clazz.getPackage().getName();
204        PackageTranslator translator = PackageTranslator.forPackage(getConfig().getTranslators(), packageName);
205
206        if (translator != null)
207            clientType = clientType.translatePackage(translator);
208
209        return clientType;
210        }
211
212        @Override
213        public JavaImport getJavaImport(Class<?> clazz) {
214        JavaImport javaImport = javaImports.get(clazz.getName());
215        if (javaImport == null) {
216            URL url = ClassUtil.findResource(clazz);
217            javaImport = new JavaImport(this, clazz, url, PropertyType.SIMPLE);
218                javaImports.put(clazz.getName(), javaImport);
219        }
220        return javaImport;
221        }
222
223        @Override
224        public Set<JavaImport> getJavaImports(ClientType clientType, boolean property) {
225                Set<JavaImport> imports = new HashSet<JavaImport>();
226                
227                for (String className : clientType.getImports()) {
228                        try {
229                        JavaImport javaImport = property ? javaPropertyImports.get(className) : javaImports.get(className);
230                        if (javaImport == null) {
231                            javaImport = new ClientImport(this, className, property);
232                            if (property)
233                                javaPropertyImports.put(className, javaImport);
234                            else
235                                javaImports.put(className, javaImport);
236                        }
237                        imports.add(javaImport);
238                        }
239                        catch (Exception e) {
240                                throw new RuntimeException("Could not get imports for type " + className);
241                        }
242                }
243        return imports;
244        }
245
246        @Override
247        public JavaType getJavaType(Class<?> clazz) {
248                JavaType javaType = javaTypes.get(clazz);
249                if (javaType == null && getConfig().isGenerated(clazz)) {
250                        URL url = ClassUtil.findResource(clazz);
251                        Kind kind = getKind(clazz);
252                        switch (kind) {
253                        case ENUM:
254                    javaType = new JavaEnum(this, clazz, url);
255                    break;
256                        case REMOTE_DESTINATION:
257                                if (getConfig().getRemoteDestinationFactory() != null)
258                                        javaType = getConfig().getRemoteDestinationFactory().newRemoteDestination(this, clazz, url);
259                                else
260                                        throw new RuntimeException("Remote destination could not be handled for " + clazz);
261                                break;
262                        case INTERFACE:
263                    javaType = new JavaInterface(this, clazz, url);
264                    break;
265                        case ENTITY:
266                                javaType = getConfig().getEntityFactory().newEntity(this, clazz, url);
267                    break;
268                        case BEAN:
269                                javaType = getConfig().getEntityFactory().newBean(this, clazz, url);
270                    break;
271                default:
272                        throw new RuntimeException("Uknown class kind: " + kind);
273                        }
274                javaTypes.put(clazz, javaType);
275                }
276                return javaType;
277        }
278        
279        @Override
280        public Kind getKind(Class<?> clazz) {
281        if (clazz.isEnum() || Enum.class.getName().equals(clazz.getName()))
282            return Kind.ENUM;
283        if (getConfig().getRemoteDestinationFactory() != null) {
284                if (getConfig().getRemoteDestinationFactory().isRemoteDestination(clazz))
285                        return Kind.REMOTE_DESTINATION;
286        }
287        if (clazz.isInterface())
288            return Kind.INTERFACE;
289        if (getConfig().getEntityFactory().isEntity(clazz))
290                return Kind.ENTITY;
291        return Kind.BEAN;
292        }
293        
294        protected GenerationType getGenerationType(Class<?> clazz) {
295                return getGenerationType(getKind(clazz), clazz);
296        }
297        @Override
298        public GenerationType getGenerationType(Kind kind, Class<?> clazz) {
299                if (!getConfig().isGenerated(clazz))
300                        return GenerationType.NOT_GENERATED;
301                TemplateUri[] uris = getConfig().getTemplateUris(kind, clazz);
302                if (uris == null || uris.length == 0)
303                        return GenerationType.NOT_GENERATED;
304                return uris.length == 1 ? GenerationType.GENERATED_SINGLE : GenerationType.GENERATED_WITH_BASE;
305        }
306
307        @Override
308        public List<JavaInterface> getJavaTypeInterfaces(Class<?> clazz) {
309        List<JavaInterface> interfazes = new ArrayList<JavaInterface>();
310        for (Class<?> interfaze : clazz.getInterfaces()) {
311            if (getConfig().isGenerated(interfaze)) {
312                JavaType javaType = getJavaType(interfaze);
313                if (javaType instanceof JavaRemoteDestination)
314                        javaType = ((JavaRemoteDestination)javaType).convertToJavaInterface();
315                interfazes.add((JavaInterface)javaType);
316            }
317        }
318        return interfazes;
319        }
320
321        @Override
322        public JavaType getJavaTypeSuperclass(Class<?> clazz) {
323        Class<?> superclass = clazz.getSuperclass();
324        if (superclass != null && getConfig().isGenerated(superclass))
325            return getJavaType(superclass);
326        return null;
327        }
328
329        @Override
330        public boolean isId(JavaFieldProperty fieldProperty) {
331                return getConfig().getEntityFactory().isId(fieldProperty);
332        }
333
334        @Override
335        public boolean isUid(JavaProperty property) {
336        return getConfig().getUid() == null
337                        ? "uid".equals(property.getName())
338                        : getConfig().getUid().equals(property.getName());
339        }
340        
341        @Override
342        public boolean isVersion(JavaProperty property) {
343                return getConfig().getEntityFactory().isVersion(property);
344        }
345        
346        @Override
347        public boolean isLazy(JavaProperty property) {
348                return getConfig().getEntityFactory().isLazy(property);
349        }
350        
351        static class PublicByteArrayOutputStream extends ByteArrayOutputStream {
352                public PublicByteArrayOutputStream() {
353                }
354                public PublicByteArrayOutputStream(int size) {
355                        super(size);
356                }
357                public byte[] getBytes() {
358                        return buf; // no copy...
359                }
360        }
361}