Project

General

Profile

Improvement #1547 » ComponentJavaDoc.java

Henning Blohm, 07.11.2013 14:39

 
1
/*
2
 * z2-Environment
3
 * 
4
 * Copyright(c) ZFabrik Software KG
5
 * 
6
 * contact@zfabrik.de
7
 * 
8
 * http://www.z2-environment.eu
9
 */
10
package com.zfabrik.impl.javadoc;
11

    
12
import java.io.BufferedWriter;
13
import java.io.File;
14
import java.io.FileFilter;
15
import java.io.FileWriter;
16
import java.io.IOException;
17
import java.lang.reflect.Method;
18
import java.net.URL;
19
import java.net.URLClassLoader;
20
import java.util.HashSet;
21
import java.util.LinkedList;
22
import java.util.List;
23
import java.util.Set;
24
import java.util.StringTokenizer;
25
import java.util.logging.Logger;
26

    
27
import com.zfabrik.components.IComponentDescriptor;
28
import com.zfabrik.components.IComponentsLookup;
29
import com.zfabrik.components.IComponentsManager;
30
import com.zfabrik.components.java.IJavaComponent;
31
import com.zfabrik.components.java.JavaComponentUtil;
32
import com.zfabrik.components.provider.util.LockingRevFile;
33
import com.zfabrik.util.fs.FileUtils;
34
import com.zfabrik.util.html.Escaper;
35

    
36
public class ComponentJavaDoc {
37
	public final static short TYPE_API  = 1;
38
	public final static short TYPE_IMPL = 2;
39
	public final static short TYPE_BOTH = 3;
40
	
41
	private static final FileFilter FOLDER_OR_JAVA_AND_NOT_HIDDEN = new FileFilter() {
42
		public boolean accept(File pathname) {
43
			return (pathname.isDirectory() || pathname.getName().endsWith(".java")) && !pathname.getName().startsWith(".");
44
		}
45
	};
46
	
47
	private static final String IMPLEMENTATION_VERSION = "11";
48
	
49
	private static class InvokeContext {
50
		ClassLoader cl;
51
		Method		m;
52
	}
53
	
54
	private short type;
55
	private String component;
56
	private boolean resolved;
57
	private File root;
58
	
59
	public ComponentJavaDoc(String component,short type) {
60
		super();
61
		this.type = type;
62
		this.component = component;
63
	}
64
	
65
	
66
	public File getRootFolder() {
67
		this.resolve();
68
		return this.root;
69
	}
70
	
71
	private File resolve() {
72
		if (!this.resolved) {
73
			this.resolved = true;
74
			this.root = getJavaDoc(this.component,this.type);
75
		}
76
		return this.root;
77
	}
78
	
79
	private static File getJavaDocFolderInComponentFolder(File cf, short type) {
80
		switch (type) {
81
		case TYPE_IMPL:
82
			return new File(cf,"gen/doc.impl");
83
		case TYPE_API:
84
			return new File(cf,"gen/doc.api");
85
		case TYPE_BOTH:
86
			return new File(cf,"gen/doc.both");
87
		}
88
		throw new IllegalArgumentException();
89
	}
90
	
91
	private static synchronized File getJavaDoc(String component, short type)  {
92
		try {
93
			// need to check.
94
			// rely on the java components logic. Look it up as Java Component to
95
			// force compilation check
96
			IJavaComponent jc = IComponentsLookup.INSTANCE.lookup(component, IJavaComponent.class);
97
			if (jc!=null) {
98
				IComponentDescriptor desc = IComponentsManager.INSTANCE.getComponent(component);
99
				File croot = IComponentsManager.INSTANCE.retrieve(component);
100
				File jdoc  = getJavaDocFolderInComponentFolder(croot, type);
101
				if (Boolean.parseBoolean(desc.getProperty(IJavaComponent.NOBUILD))) {
102
					// no jdoc gen for no build components
103
					if (jdoc.exists()) {
104
						return jdoc;
105
					}
106
					return null;
107
				} else {
108
					// check revision
109
					String revfile = "doc_"+qualifier(type)+".rev";
110
					LockingRevFile lrf = new LockingRevFile(new File(croot,revfile));
111
					lrf.open();
112
					try {
113
						if (jdoc.exists() && IMPLEMENTATION_VERSION.equals(lrf.properties().get("v"))) {
114
							// javadocs exist, no build component or right version 
115
							return jdoc;
116
						} else {
117
							logger.info("Updating Javadoc for component "+component+" ("+qualifier(type)+")");
118
							// no jdocs or wrong implementation version
119
							// must generate javadocs
120
							// check javadoc tool
121
							InvokeContext ic = getTool();
122
							if (ic==null) {
123
								return null;
124
							}
125
							// compute params
126
							List<String> params = new LinkedList<String>();
127
							List<File> files = new LinkedList<File>();
128
							
129
							// 1. source paths and packages
130
							List<File> fs = new LinkedList<File>();
131
							if ((type & TYPE_IMPL)!= 0) {
132
								File sf = new File(croot,"src.impl");
133
								if (sf.exists()) {
134
									fs.add(sf);
135
								}
136
							} 
137
							if ((type & TYPE_API)!= 0) {
138
								File sf = new File(croot,"src.api");
139
								if (sf.exists()) {
140
									fs.add(sf);
141
								}
142
							}
143
			
144
							if (!fs.isEmpty()) {
145
								// 1a. ok, there is something to do
146
								StringBuilder sourcepath = new StringBuilder(200);
147
								for (File f : fs) {
148
									// add as source path and
149
									// list all packages
150
									int l = files.size();
151
									addSourceFiles(files,f);
152
									if (l<files.size()) {
153
										sourcepath.append((sourcepath.length()>0? File.pathSeparator:"")).append(f.getCanonicalPath());
154
									}
155
								}
156
								params.add("-sourcepath");
157
								params.add(sourcepath.toString());
158
								
159
								// 2. deps translate to -linkoffline relations
160
								addLinks(component, new HashSet<String>(), params, type, true);							
161
								
162
								params.add("-link");
163
								params.add("http://download.oracle.com/javase/6/docs/api");
164
								
165
								// 3. class path
166
								StringBuilder classpath = new StringBuilder(200);
167
								// add the classpath
168
								addToPath(classpath, ((type & TYPE_IMPL)!=0? jc.getPrivateLoader().getURLs() : jc.getPublicLoader().getURLs()));
169

    
170
								params.add("-classpath");
171
								params.add(classpath.toString());
172
								
173
								// 4. outpath
174
								FileUtils.delete(jdoc);
175
								jdoc.mkdirs();
176
								params.add("-d");
177
								params.add(jdoc.getCanonicalPath());
178
								
179
								if (!files.isEmpty()) {
180
									// 5. add files to generate javadoc for
181
									File fileList = File.createTempFile("javadoc_",".lst");
182
									BufferedWriter w = new BufferedWriter(new FileWriter(fileList));
183
									try {
184
										for (File sf : files) {
185
											w.write(sf.getAbsolutePath());
186
											w.newLine();
187
										}
188
									} finally {
189
										w.close();
190
									}
191
									params.add("@"+fileList.getAbsolutePath());
192
									
193
									try {
194
										// 6. and GO!
195
										logger.info("Running Javadoc command with command line: "+params);
196
										ClassLoader ol = Thread.currentThread().getContextClassLoader();
197
										Thread.currentThread().setContextClassLoader(ic.cl);
198
										try {
199
											
200
											int rc = (Integer) ic.m.invoke(null, new Object[]{params.toArray(new String[params.size()])});
201
											if (rc==0 && !empty(jdoc)) {
202
												// done
203
												lrf.properties().setProperty("v", IMPLEMENTATION_VERSION);
204
												lrf.update();
205
												return jdoc;
206
											}
207
										} finally {
208
											Thread.currentThread().setContextClassLoader(ol);
209
										}
210
									} finally {
211
										fileList.delete();
212
									}
213
								} else {
214
									logger.info("Found no packages: Nothing to do");
215
								}
216
							} 
217
						}
218
					} finally {
219
						lrf.close();
220
					}
221
				}
222
			}
223
		} catch (Exception e) {
224
			throw new RuntimeException("Javadoc generation failed",e);
225
		}
226
		return null;
227
	}
228

    
229

    
230
	private static String qualifier(short type) {
231
		return type==TYPE_IMPL?"impl":type==TYPE_API?"api":"both";
232
	}
233

    
234

    
235
	private static void addToPath(StringBuilder classpath, URL[] urls) {
236
		if (urls!=null) {
237
			for (URL u : urls) {
238
				if (classpath.length()>0) {
239
					classpath.append(File.pathSeparator);
240
				}
241
				classpath.append(u.getPath());
242
			}
243
		}
244
	}
245

    
246
	private static boolean empty(File tf) {
247
		return (!tf.isDirectory() || tf.list().length==0);
248
	}
249

    
250

    
251
	//
252
	// find the actual javadoc implementation 
253
	//
254
	private static InvokeContext getTool() throws Exception {
255
		String jhs = System.getenv("JAVA_HOME");
256
		if (jhs==null) {
257
			jhs = System.getProperty("java.home");
258
			if (jhs.endsWith("/jre")) {
259
				// try one level up
260
				jhs = jhs.substring(0,jhs.length()-4);
261
			}
262
		}
263
		File jh = new File(jhs);
264
		File tj = new File(jh,"lib/tools.jar");
265
		ClassLoader cl = ComponentJavaDoc.class.getClassLoader();
266
		if (!tj.exists()) {
267
			logger.warning("Library tools.jar not found at "+tj.getCanonicalPath()+" will try to load Javadoc tool directly");
268
		} else {
269
			cl = new URLClassLoader(new URL[]{tj.toURI().toURL()}, cl);
270
		}
271
		try {
272
			Class<?> jdclz = Class.forName("com.sun.tools.javadoc.Main",false,cl);
273
			InvokeContext r = new InvokeContext();
274
			r.cl = cl;
275
			r.m=jdclz.getMethod("execute",String[].class);
276
			return r;
277
		} catch (Exception e) {
278
			logger.warning("Failed to load JavaDoc tool class (are you running a JDK?) - cannot update javadocs ("+e+")");
279
			return null;
280
		}
281
	}
282

    
283
	//
284
	// recursively find package names in a folder hierarchy
285
	//
286
	private static void addSourceFiles(List<File> files, File f) {
287
		for (File g : f.listFiles(FOLDER_OR_JAVA_AND_NOT_HIDDEN)) {
288
			if (g.isDirectory()) {
289
				addSourceFiles(files,g);
290
			} else {
291
				files.add(g);
292
			}
293
		}
294
	}
295

    
296
	//
297
	// add external refs as link params following the 
298
	// java refs
299
	//
300
	private static void addLinks(String component, Set<String> traversed, List<String> params, short type, boolean start) throws IOException {
301
		component = JavaComponentUtil.fixJavaComponentName(component);
302
		if (traversed.contains(component+"@"+type)) {
303
			// already traversed.
304
			return;
305
		}
306
		traversed.add(component+"@"+type);
307
		
308
		IComponentDescriptor desc = IComponentsManager.INSTANCE.getComponent(component);
309
		if (desc==null) {
310
			return;
311
		}
312
		
313
		// deps 
314
		addAllLinks(traversed, params, desc.getProperty(IJavaComponent.PUBREFS));
315
		if (start && (type&TYPE_IMPL)!=0) {
316
			// add private references
317
			addAllLinks(traversed, params, desc.getProperty(IJavaComponent.PRIREFS));
318
			// add our own API
319
			addLinks(component, traversed, params, TYPE_API,false);
320
		}
321
		
322
		if (start) {
323
			// at start of recursion add the always present core api
324
			// add the core api
325
			addLinks("com.zfabrik.core.api",traversed,params,TYPE_API,false);
326
		} else {
327
			// otherwise add the actual API
328
			File f = getJavaDoc(component, TYPE_API);
329
			if (f!=null) {
330
				params.add("-linkoffline");
331
				params.add("/javadoc/"+Escaper.urlEncode(component,'!')+"/api");
332
				params.add(f.toURI().toString());
333
			}
334
		}
335
	}
336

    
337
	
338
	private static void addAllLinks(Set<String> traversed, List<String> params,String refs) throws IOException {
339
		if (refs!=null) {
340
			StringTokenizer tk = new StringTokenizer(refs);
341
			while (tk.hasMoreTokens()) {
342
				addLinks(tk.nextToken().trim(), traversed, params, TYPE_API,false);
343
			}
344
		}
345
	}
346

    
347

    
348
	private final static Logger logger = Logger.getLogger(ComponentJavaDoc.class.getName());
349
}
(2-2/2)