001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 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 */
022package org.granite.generator.as3;
023
024import java.io.ByteArrayOutputStream;
025import java.io.File;
026import java.io.IOException;
027import java.io.OutputStream;
028import java.io.OutputStreamWriter;
029import java.io.PrintWriter;
030import java.lang.reflect.ParameterizedType;
031import java.lang.reflect.Type;
032import java.net.URL;
033import java.text.DateFormat;
034import java.util.ArrayList;
035import java.util.Date;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041
042import org.granite.generator.Generator;
043import org.granite.generator.Input;
044import org.granite.generator.Listener;
045import org.granite.generator.TemplateUri;
046import org.granite.generator.as3.reflect.ClientImport;
047import org.granite.generator.as3.reflect.JavaAbstractType.GenerationType;
048import org.granite.generator.as3.reflect.JavaEnum;
049import org.granite.generator.as3.reflect.JavaFieldProperty;
050import org.granite.generator.as3.reflect.JavaImport;
051import org.granite.generator.as3.reflect.JavaInterface;
052import org.granite.generator.as3.reflect.JavaProperty;
053import org.granite.generator.as3.reflect.JavaRemoteDestination;
054import org.granite.generator.as3.reflect.JavaType;
055import org.granite.generator.as3.reflect.JavaType.Kind;
056import org.granite.generator.as3.reflect.JavaTypeFactory;
057import org.granite.generator.exception.TemplateException;
058import org.granite.generator.exception.TemplateUriException;
059import org.granite.generator.gsp.AbstractGroovyTransformer;
060import org.granite.generator.gsp.GroovyTemplate;
061import org.granite.util.ClassUtil;
062
063/**
064 * @author Franck WOLFF
065 */
066public abstract class JavaClientGroovyTransformer
067        extends AbstractGroovyTransformer<JavaAs3Input, JavaAs3Output, JavaAs3GroovyConfiguration>
068        implements JavaTypeFactory {
069
070        private static final String GENERATED_BASE_SUFFIX = "Base";
071        
072        protected final Map<Class<?>, JavaType> javaTypes = new HashMap<Class<?>, JavaType>();
073    protected final Map<String, JavaImport> javaImports = new HashMap<String, JavaImport>();
074    protected final Map<String, JavaImport> javaPropertyImports = new HashMap<String, JavaImport>();
075
076        public JavaClientGroovyTransformer() {
077        }
078
079        public JavaClientGroovyTransformer(JavaAs3GroovyConfiguration config, Listener listener) {
080                super(config, listener);
081        }
082
083        @Override
084        public boolean accept(Input<?> input) {
085                return (input instanceof JavaAs3Input);
086        }
087
088        @Override
089        protected JavaAs3Output[] getOutputs(JavaAs3Input input) throws IOException, TemplateUriException {
090                JavaType javaType = getJavaType(input.getType());
091                input.setJavaType(javaType);
092                TemplateUri[] templateUris = getTemplateUris(javaType);
093                boolean hasBaseTemplate = templateUris.length > 1;
094                
095                JavaAs3Output[] outputs = new JavaAs3Output[templateUris.length];
096                
097                for (int i = 0; i < templateUris.length; i++) {
098                        GroovyTemplate template = getTemplate(templateUris[i]);
099                        File dir = getOutputDir(input, template);
100                        File file = getOutputFile(input, template, dir);
101                        boolean outdated = isOutdated(input, template, file, hasBaseTemplate);
102                        String status = getOutputStatus(input, template, file, hasBaseTemplate);
103                        
104                        outputs[i] = new JavaAs3Output(
105                                javaType,
106                                template,
107                                dir,
108                                file,
109                                outdated,
110                                status
111                        );
112                }
113                
114                return outputs;
115        }
116
117        @Override
118        protected void generate(JavaAs3Input input, JavaAs3Output output) throws IOException, TemplateException {
119                Map<String, Object> bindings = getBindings(input, output);
120                
121                // Write in memory (step 1).
122                PublicByteArrayOutputStream pbaos = new PublicByteArrayOutputStream(8192);
123                output.getTemplate().execute(bindings, new PrintWriter(new OutputStreamWriter(pbaos, "UTF-8")));
124                
125                // If no exceptions were raised, write to file (step 2).
126                OutputStream stream = null;
127                try {
128                        stream = output.openStream();
129                        stream.write(pbaos.getBytes(), 0, pbaos.size());
130                } finally {
131                        if (stream != null)
132                                stream.close();
133                }
134        }
135
136    protected Map<String, Object> getBindings(JavaAs3Input input, JavaAs3Output output) {
137        Map<String, Object> bindings = new HashMap<String, Object>();
138        bindings.put("gVersion", Generator.VERSION);
139        bindings.put("jClass", input.getJavaType());
140        bindings.put("fAttributes", input.getAttributes());
141        return bindings;
142    }
143
144    protected TemplateUri[] getTemplateUris(JavaType javaType) {
145        return getConfig().getTemplateUris(getKind(javaType.getType()), javaType.getClass());
146    }
147    
148    protected File getOutputDir(JavaAs3Input input, GroovyTemplate template) {
149        return (template.isBase() ? getConfig().getBaseOutputDir(input) : getConfig().getOutputDir(input));
150    }
151
152    protected abstract File getOutputFile(JavaAs3Input input, GroovyTemplate template, File outputDir);
153    
154    protected String getOutputFileSuffix(JavaAs3Input input, GroovyTemplate template) {
155        return template.isBase() ? GENERATED_BASE_SUFFIX : "";
156    }
157    
158    protected boolean isOutdated(JavaAs3Input input, GroovyTemplate template, File outputFile, boolean hasBaseTemplate) {
159        if (!outputFile.exists())
160                return true;
161        if (outputFile.lastModified() > System.currentTimeMillis()) {
162                getListener().warn(
163                                outputFile.getAbsolutePath() +
164                                " has a last modified time in the future: " +
165                                DateFormat.getInstance().format(new Date(outputFile.lastModified()))
166                );
167        }
168        if (!template.isBase() && hasBaseTemplate)
169                return false;
170        return input.getFile().lastModified() > outputFile.lastModified();
171    }
172    
173    protected String getOutputStatus(JavaAs3Input input, GroovyTemplate template, File outputFile, boolean hasBaseTemplate) {
174        if (!outputFile.exists())
175                return Listener.MSG_FILE_NOT_EXISTS;
176        if (!template.isBase() && hasBaseTemplate)
177                return Listener.MSG_FILE_EXISTS_NO_OVER;
178        if (input.getFile().lastModified() > outputFile.lastModified())
179                return Listener.MSG_FILE_OUTDATED;
180        return Listener.MSG_FILE_UPTODATE;
181    }
182
183        @Override
184        public ClientType getClientType(Type type, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) {
185        Class<?> clazz = ClassUtil.classOfType(type);
186        
187        ClientType clientType = getConfig().getAs3TypeFactory().getClientType(type, declaringClass, declaringTypes, propertyType);
188        if (clientType == null)
189                clientType = getConfig().getAs3TypeFactory().getAs3Type(clazz);
190        
191        if (clientType != null)
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                if (clientType == null)
228                        return imports;
229                
230                for (String className : clientType.getImports()) {
231                        try {
232                        JavaImport javaImport = property ? javaPropertyImports.get(className) : javaImports.get(className);
233                        if (javaImport == null) {
234                            javaImport = new ClientImport(this, className, property);
235                            if (property)
236                                javaPropertyImports.put(className, javaImport);
237                            else
238                                javaImports.put(className, javaImport);
239                        }
240                        imports.add(javaImport);
241                        }
242                        catch (Exception e) {
243                                throw new RuntimeException("Could not get imports for type " + className);
244                        }
245                }
246        return imports;
247        }
248
249        @Override
250        public JavaType getJavaType(Class<?> clazz) {
251                JavaType javaType = javaTypes.get(clazz);
252                if (javaType == null && getConfig().isGenerated(clazz)) {
253                        URL url = ClassUtil.findResource(clazz);
254                        Kind kind = getKind(clazz);
255                        switch (kind) {
256                        case ENUM:
257                    javaType = new JavaEnum(this, clazz, url);
258                    break;
259                        case REMOTE_DESTINATION:
260                                if (getConfig().getRemoteDestinationFactory() != null)
261                                        javaType = getConfig().getRemoteDestinationFactory().newRemoteDestination(this, clazz, url);
262                                else
263                                        throw new RuntimeException("Remote destination could not be handled for " + clazz);
264                                break;
265                        case INTERFACE:
266                    javaType = new JavaInterface(this, clazz, url);
267                    break;
268                        case ENTITY:
269                                javaType = getConfig().getEntityFactory().newEntity(this, clazz, url);
270                    break;
271                        case BEAN:
272                                javaType = getConfig().getEntityFactory().newBean(this, clazz, url);
273                    break;
274                default:
275                        throw new RuntimeException("Uknown class kind: " + kind);
276                        }
277                javaTypes.put(clazz, javaType);
278                }
279                return javaType;
280        }
281        
282        @Override
283        public Kind getKind(Class<?> clazz) {
284        if (clazz.isEnum() || Enum.class.getName().equals(clazz.getName()))
285            return Kind.ENUM;
286        if (getConfig().getRemoteDestinationFactory() != null) {
287                if (getConfig().getRemoteDestinationFactory().isRemoteDestination(clazz))
288                        return Kind.REMOTE_DESTINATION;
289        }
290        if (clazz.isInterface())
291            return Kind.INTERFACE;
292        if (getConfig().getEntityFactory().isEntity(clazz))
293                return Kind.ENTITY;
294        return Kind.BEAN;
295        }
296        
297        protected GenerationType getGenerationType(Class<?> clazz) {
298                return getGenerationType(getKind(clazz), clazz);
299        }
300        @Override
301        public GenerationType getGenerationType(Kind kind, Class<?> clazz) {
302                if (!getConfig().isGenerated(clazz))
303                        return GenerationType.NOT_GENERATED;
304                TemplateUri[] uris = getConfig().getTemplateUris(kind, clazz);
305                if (uris == null || uris.length == 0)
306                        return GenerationType.NOT_GENERATED;
307                return uris.length == 1 ? GenerationType.GENERATED_SINGLE : GenerationType.GENERATED_WITH_BASE;
308        }
309
310        @Override
311        public List<JavaInterface> getJavaTypeInterfaces(Class<?> clazz) {
312        List<JavaInterface> interfazes = new ArrayList<JavaInterface>();
313        for (Class<?> interfaze : clazz.getInterfaces()) {
314            if (getConfig().isGenerated(interfaze)) {
315                JavaType javaType = getJavaType(interfaze);
316                if (javaType instanceof JavaRemoteDestination)
317                        javaType = ((JavaRemoteDestination)javaType).convertToJavaInterface();
318                interfazes.add((JavaInterface)javaType);
319            }
320        }
321        return interfazes;
322        }
323
324        @Override
325        public JavaType getJavaTypeSuperclass(Class<?> clazz) {
326        Class<?> superclass = clazz.getSuperclass();
327        if (superclass != null && getConfig().isGenerated(superclass))
328            return getJavaType(superclass);
329        return null;
330        }
331
332        @Override
333        public boolean isId(JavaFieldProperty fieldProperty) {
334                return getConfig().getEntityFactory().isId(fieldProperty);
335        }
336
337        @Override
338        public boolean isUid(JavaProperty property) {
339        return getConfig().getUid() == null
340                        ? "uid".equals(property.getName())
341                        : getConfig().getUid().equals(property.getName());
342        }
343        
344        @Override
345        public boolean isVersion(JavaProperty property) {
346                return getConfig().getEntityFactory().isVersion(property);
347        }
348        
349        @Override
350        public boolean isLazy(JavaProperty property) {
351                return getConfig().getEntityFactory().isLazy(property);
352        }
353        
354        static class PublicByteArrayOutputStream extends ByteArrayOutputStream {
355                public PublicByteArrayOutputStream() {
356                }
357                public PublicByteArrayOutputStream(int size) {
358                        super(size);
359                }
360                public byte[] getBytes() {
361                        return buf; // no copy...
362                }
363        }
364}