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