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    
023    package org.granite.generator.as3;
024    
025    import java.io.ByteArrayOutputStream;
026    import java.io.File;
027    import java.io.IOException;
028    import java.io.OutputStream;
029    import java.io.OutputStreamWriter;
030    import java.io.PrintWriter;
031    import java.lang.reflect.ParameterizedType;
032    import java.lang.reflect.Type;
033    import java.net.URL;
034    import java.text.DateFormat;
035    import java.util.ArrayList;
036    import java.util.Date;
037    import java.util.HashMap;
038    import java.util.HashSet;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.Set;
042    
043    import org.granite.generator.Generator;
044    import org.granite.generator.Input;
045    import org.granite.generator.Listener;
046    import org.granite.generator.TemplateUri;
047    import org.granite.generator.as3.reflect.ClientImport;
048    import org.granite.generator.as3.reflect.JavaAbstractType.GenerationType;
049    import org.granite.generator.as3.reflect.JavaEnum;
050    import org.granite.generator.as3.reflect.JavaFieldProperty;
051    import org.granite.generator.as3.reflect.JavaImport;
052    import org.granite.generator.as3.reflect.JavaInterface;
053    import org.granite.generator.as3.reflect.JavaProperty;
054    import org.granite.generator.as3.reflect.JavaRemoteDestination;
055    import org.granite.generator.as3.reflect.JavaType;
056    import org.granite.generator.as3.reflect.JavaType.Kind;
057    import org.granite.generator.as3.reflect.JavaTypeFactory;
058    import org.granite.generator.exception.TemplateException;
059    import org.granite.generator.exception.TemplateUriException;
060    import org.granite.generator.gsp.AbstractGroovyTransformer;
061    import org.granite.generator.gsp.GroovyTemplate;
062    import org.granite.util.ClassUtil;
063    
064    /**
065     * @author Franck WOLFF
066     */
067    public 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    }