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    
021    package org.granite.generator.as3;
022    
023    import java.io.ByteArrayOutputStream;
024    import java.io.File;
025    import java.io.IOException;
026    import java.io.OutputStream;
027    import java.io.OutputStreamWriter;
028    import java.io.PrintWriter;
029    import java.lang.reflect.ParameterizedType;
030    import java.lang.reflect.Type;
031    import java.net.URL;
032    import java.text.DateFormat;
033    import java.util.ArrayList;
034    import java.util.Date;
035    import java.util.HashMap;
036    import java.util.HashSet;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Set;
040    
041    import org.granite.generator.Generator;
042    import org.granite.generator.Input;
043    import org.granite.generator.Listener;
044    import org.granite.generator.TemplateUri;
045    import org.granite.generator.as3.reflect.ClientImport;
046    import org.granite.generator.as3.reflect.JavaAbstractType.GenerationType;
047    import org.granite.generator.as3.reflect.JavaEnum;
048    import org.granite.generator.as3.reflect.JavaFieldProperty;
049    import org.granite.generator.as3.reflect.JavaImport;
050    import org.granite.generator.as3.reflect.JavaInterface;
051    import org.granite.generator.as3.reflect.JavaProperty;
052    import org.granite.generator.as3.reflect.JavaRemoteDestination;
053    import org.granite.generator.as3.reflect.JavaType;
054    import org.granite.generator.as3.reflect.JavaType.Kind;
055    import org.granite.generator.as3.reflect.JavaTypeFactory;
056    import org.granite.generator.exception.TemplateException;
057    import org.granite.generator.exception.TemplateUriException;
058    import org.granite.generator.gsp.AbstractGroovyTransformer;
059    import org.granite.generator.gsp.GroovyTemplate;
060    import org.granite.util.ClassUtil;
061    
062    /**
063     * @author Franck WOLFF
064     */
065    public 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, boolean property) {
184            Class<?> clazz = ClassUtil.classOfType(type);
185            
186            ClientType clientType = getConfig().getAs3TypeFactory().getClientType(type, declaringClass, declaringTypes, property);
187            if (clientType == null)
188                    clientType = getConfig().getAs3TypeFactory().getAs3Type(clazz);
189            
190            if (getConfig().getTranslators().isEmpty() || clazz.getPackage() == null)
191                return clientType;
192    
193            String packageName = clazz.getPackage().getName();
194            PackageTranslator translator = PackageTranslator.forPackage(getConfig().getTranslators(), packageName);
195    
196            if (translator != null)
197                clientType = clientType.translatePackage(translator);
198    
199            return clientType;
200            }
201    
202            @Override
203            public ClientType getAs3Type(Class<?> clazz) {
204            ClientType clientType = getConfig().getAs3TypeFactory().getAs3Type(clazz);
205            if (getConfig().getTranslators().isEmpty() || clazz.getPackage() == null)
206                return clientType;
207    
208            String packageName = clazz.getPackage().getName();
209            PackageTranslator translator = PackageTranslator.forPackage(getConfig().getTranslators(), packageName);
210    
211            if (translator != null)
212                clientType = clientType.translatePackage(translator);
213    
214            return clientType;
215            }
216    
217            @Override
218            public JavaImport getJavaImport(Class<?> clazz) {
219            JavaImport javaImport = javaImports.get(clazz.getName());
220            if (javaImport == null) {
221                URL url = ClassUtil.findResource(clazz);
222                javaImport = new JavaImport(this, clazz, url, false);
223                    javaImports.put(clazz.getName(), javaImport);
224            }
225            return javaImport;
226            }
227    
228            @Override
229            public Set<JavaImport> getJavaImports(ClientType clientType, boolean property) {
230                    Set<JavaImport> imports = new HashSet<JavaImport>();
231                    
232                    for (String className : clientType.getImports()) {
233                            try {
234                            JavaImport javaImport = property ? javaPropertyImports.get(className) : javaImports.get(className);
235                            if (javaImport == null) {
236                                javaImport = new ClientImport(this, className, property);
237                                if (property)
238                                    javaPropertyImports.put(className, javaImport);
239                                else
240                                    javaImports.put(className, javaImport);
241                            }
242                            imports.add(javaImport);
243                            }
244                            catch (Exception e) {
245                                    throw new RuntimeException("Could not get imports for type " + className);
246                            }
247                    }
248            return imports;
249            }
250    
251            @Override
252            public JavaType getJavaType(Class<?> clazz) {
253                    JavaType javaType = javaTypes.get(clazz);
254                    if (javaType == null && getConfig().isGenerated(clazz)) {
255                            URL url = ClassUtil.findResource(clazz);
256                            Kind kind = getKind(clazz);
257                            switch (kind) {
258                            case ENUM:
259                        javaType = new JavaEnum(this, clazz, url);
260                        break;
261                            case REMOTE_DESTINATION:
262                                    if (getConfig().getRemoteDestinationFactory() != null)
263                                            javaType = getConfig().getRemoteDestinationFactory().newRemoteDestination(this, clazz, url);
264                                    else
265                                            throw new RuntimeException("Remote destination could not be handled for " + clazz);
266                                    break;
267                            case INTERFACE:
268                        javaType = new JavaInterface(this, clazz, url);
269                        break;
270                            case ENTITY:
271                                    javaType = getConfig().getEntityFactory().newEntity(this, clazz, url);
272                        break;
273                            case BEAN:
274                                    javaType = getConfig().getEntityFactory().newBean(this, clazz, url);
275                        break;
276                    default:
277                            throw new RuntimeException("Uknown class kind: " + kind);
278                            }
279                    javaTypes.put(clazz, javaType);
280                    }
281                    return javaType;
282            }
283            
284            @Override
285            public Kind getKind(Class<?> clazz) {
286            if (clazz.isEnum() || Enum.class.getName().equals(clazz.getName()))
287                return Kind.ENUM;
288            if (getConfig().getRemoteDestinationFactory() != null) {
289                    if (getConfig().getRemoteDestinationFactory().isRemoteDestination(clazz))
290                            return Kind.REMOTE_DESTINATION;
291            }
292            if (clazz.isInterface())
293                return Kind.INTERFACE;
294            if (getConfig().getEntityFactory().isEntity(clazz))
295                    return Kind.ENTITY;
296            return Kind.BEAN;
297            }
298            
299            protected GenerationType getGenerationType(Class<?> clazz) {
300                    return getGenerationType(getKind(clazz), clazz);
301            }
302            @Override
303            public GenerationType getGenerationType(Kind kind, Class<?> clazz) {
304                    if (!getConfig().isGenerated(clazz))
305                            return GenerationType.NOT_GENERATED;
306                    TemplateUri[] uris = getConfig().getTemplateUris(kind, clazz);
307                    if (uris == null || uris.length == 0)
308                            return GenerationType.NOT_GENERATED;
309                    return uris.length == 1 ? GenerationType.GENERATED_SINGLE : GenerationType.GENERATED_WITH_BASE;
310            }
311    
312            @Override
313            public List<JavaInterface> getJavaTypeInterfaces(Class<?> clazz) {
314            List<JavaInterface> interfazes = new ArrayList<JavaInterface>();
315            for (Class<?> interfaze : clazz.getInterfaces()) {
316                if (getConfig().isGenerated(interfaze)) {
317                    JavaType javaType = getJavaType(interfaze);
318                    if (javaType instanceof JavaRemoteDestination)
319                            javaType = ((JavaRemoteDestination)javaType).convertToJavaInterface();
320                    interfazes.add((JavaInterface)javaType);
321                }
322            }
323            return interfazes;
324            }
325    
326            @Override
327            public JavaType getJavaTypeSuperclass(Class<?> clazz) {
328            Class<?> superclass = clazz.getSuperclass();
329            if (superclass != null && getConfig().isGenerated(superclass))
330                return getJavaType(superclass);
331            return null;
332            }
333    
334            @Override
335            public boolean isId(JavaFieldProperty fieldProperty) {
336                    return getConfig().getEntityFactory().isId(fieldProperty);
337            }
338    
339            @Override
340            public boolean isUid(JavaProperty property) {
341            return getConfig().getUid() == null
342                            ? "uid".equals(property.getName())
343                            : getConfig().getUid().equals(property.getName());
344            }
345            
346            @Override
347            public boolean isVersion(JavaProperty property) {
348                    return getConfig().getEntityFactory().isVersion(property);
349            }
350            
351            @Override
352            public boolean isLazy(JavaProperty property) {
353                    return getConfig().getEntityFactory().isLazy(property);
354            }
355            
356            static class PublicByteArrayOutputStream extends ByteArrayOutputStream {
357                    public PublicByteArrayOutputStream() {
358                    }
359                    public PublicByteArrayOutputStream(int size) {
360                            super(size);
361                    }
362                    public byte[] getBytes() {
363                            return buf; // no copy...
364                    }
365            }
366    }