1 /*******************************************************************************
2  * Copyright (c) 2007, 2013 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.pde.api.tools.internal.model;
15 
16 import java.io.File;
17 import java.io.FileFilter;
18 import java.io.FileInputStream;
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
32 import org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor;
33 import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement;
34 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
35 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot;
36 import org.eclipse.pde.api.tools.internal.util.Util;
37 
38 /**
39  * An {@link IApiTypeContainer} rooted at a directory in the file system.
40  *
41  * @since 1.0.0
42  */
43 public class DirectoryApiTypeContainer extends ApiElement implements IApiTypeContainer {
44 
45 	/**
46 	 * Implementation of an {@link IApiTypeRoot} in the local file system.
47 	 */
48 	static class LocalApiTypeRoot extends AbstractApiTypeRoot implements Comparable<Object> {
49 
50 		String fLocation = null;
51 
52 		/**
53 		 * Constructs a class file on the given file
54 		 *
55 		 * @param directory the parent {@link IApiElement} directory
56 		 * @param location
57 		 * @param qualified type name
58 		 * @param component owning API component
59 		 */
LocalApiTypeRoot(DirectoryApiTypeContainer directory, String location, String typeName)60 		public LocalApiTypeRoot(DirectoryApiTypeContainer directory, String location, String typeName) {
61 			super(directory, typeName);
62 			fLocation = location;
63 		}
64 
65 		@Override
getTypeName()66 		public String getTypeName() {
67 			return getName();
68 		}
69 
70 		@Override
compareTo(Object o)71 		public int compareTo(Object o) {
72 			return getName().compareTo(((LocalApiTypeRoot) o).getName());
73 		}
74 
75 		@Override
equals(Object obj)76 		public boolean equals(Object obj) {
77 			if (obj instanceof LocalApiTypeRoot) {
78 				return ((LocalApiTypeRoot) obj).getName().equals(this.getName());
79 			}
80 			return false;
81 		}
82 
83 		@Override
hashCode()84 		public int hashCode() {
85 			return this.getName().hashCode();
86 		}
87 
88 		@Override
getContents()89 		public byte[] getContents() throws CoreException {
90 			InputStream stream = null;
91 			try {
92 				stream = new FileInputStream(new File(fLocation));
93 			} catch (FileNotFoundException e) {
94 				abort("File not found", e); //$NON-NLS-1$
95 				return null;
96 			}
97 			try {
98 				return Util.getInputStreamAsByteArray(stream, -1);
99 			} catch (IOException ioe) {
100 				abort("Unable to read class file: " + getTypeName(), ioe); //$NON-NLS-1$
101 				return null;
102 			} finally {
103 				try {
104 					stream.close();
105 				} catch (IOException e) {
106 					ApiPlugin.log(e);
107 				}
108 			}
109 		}
110 	}
111 
112 	/**
113 	 * Map of package names to associated directory (file)
114 	 */
115 	private Map<String, String> fPackages;
116 
117 	/**
118 	 * Cache of package names
119 	 */
120 	private String[] fPackageNames;
121 
122 	/**
123 	 * Constructs an {@link IApiTypeContainer} rooted at the specified path.
124 	 *
125 	 * @param parent the parent {@link IApiElement} or <code>null</code> if none
126 	 * @param location absolute path in the local file system
127 	 */
DirectoryApiTypeContainer(IApiElement parent, String location)128 	public DirectoryApiTypeContainer(IApiElement parent, String location) {
129 		super(parent, IApiElement.API_TYPE_CONTAINER, location);
130 	}
131 
132 	/**
133 	 * @see org.eclipse.pde.api.tools.internal.provisional.IApiTypeContainer#accept(org.eclipse.pde.api.tools.internal.provisional.ApiTypeContainerVisitor)
134 	 */
135 	@Override
accept(ApiTypeContainerVisitor visitor)136 	public void accept(ApiTypeContainerVisitor visitor) throws CoreException {
137 		if (visitor.visit(this)) {
138 			init();
139 			String[] packageNames = getPackageNames();
140 			for (String pkg : packageNames) {
141 				if (visitor.visitPackage(pkg)) {
142 					String location = fPackages.get(pkg);
143 					if (location == null) {
144 						continue;
145 					}
146 					File dir = new File(location);
147 					if (!dir.exists()) {
148 						continue;
149 					}
150 					File[] files = dir.listFiles((FileFilter) file -> file.isFile() && file.getName().endsWith(Util.DOT_CLASS_SUFFIX));
151 					if (files != null) {
152 						List<LocalApiTypeRoot> classFiles = new ArrayList<>();
153 						for (File file : files) {
154 							String name = file.getName();
155 							String typeName = name.substring(0, name.length() - 6);
156 							if (pkg.length() > 0) {
157 								typeName = pkg + "." + typeName; //$NON-NLS-1$
158 							}
159 							classFiles.add(new LocalApiTypeRoot(this, file.getAbsolutePath(), typeName));
160 						}
161 						Collections.sort(classFiles);
162 						for (IApiTypeRoot classFile : classFiles) {
163 							visitor.visit(pkg, classFile);
164 							visitor.end(pkg, classFile);
165 						}
166 					}
167 				}
168 				visitor.endVisitPackage(pkg);
169 			}
170 		}
171 		visitor.end(this);
172 	}
173 
174 	/**
175 	 * @see java.lang.Object#toString()
176 	 */
177 	@Override
toString()178 	public String toString() {
179 		StringBuilder buff = new StringBuilder();
180 		buff.append("Directory Class File Container: " + getName()); //$NON-NLS-1$
181 		return buff.toString();
182 	}
183 
184 	@Override
close()185 	public synchronized void close() throws CoreException {
186 		fPackages = null;
187 		fPackageNames = null;
188 	}
189 
190 	@Override
findTypeRoot(String qualifiedName)191 	public IApiTypeRoot findTypeRoot(String qualifiedName) throws CoreException {
192 		init();
193 		int index = qualifiedName.lastIndexOf('.');
194 		String cfName = qualifiedName;
195 		String pkg = Util.DEFAULT_PACKAGE_NAME;
196 		if (index > 0) {
197 			pkg = qualifiedName.substring(0, index);
198 			cfName = qualifiedName.substring(index + 1);
199 		}
200 		String location = fPackages.get(pkg);
201 		if (location != null) {
202 			File file = new File(location, cfName + Util.DOT_CLASS_SUFFIX);
203 			if (file.exists()) {
204 				return new LocalApiTypeRoot(this, file.getAbsolutePath(), qualifiedName);
205 			}
206 		}
207 		return null;
208 	}
209 
210 	@Override
getPackageNames()211 	public String[] getPackageNames() throws CoreException {
212 		init();
213 		if (fPackageNames == null) {
214 			List<String> names = new ArrayList<>(fPackages.keySet());
215 			String[] result = new String[names.size()];
216 			names.toArray(result);
217 			Arrays.sort(result);
218 			fPackageNames = result;
219 		}
220 		return fPackageNames;
221 	}
222 
223 	/**
224 	 * Builds cache of package names to directories
225 	 */
init()226 	private synchronized void init() {
227 		if (fPackages == null) {
228 			fPackages = new HashMap<>();
229 			processDirectory(Util.DEFAULT_PACKAGE_NAME, new File(getName()));
230 		}
231 	}
232 
233 	/**
234 	 * Traverses a directory to determine if it has class files and then visits
235 	 * sub-directories.
236 	 *
237 	 * @param packageName package name of directory being visited
238 	 * @param dir directory being visited
239 	 */
processDirectory(String packageName, File dir)240 	private void processDirectory(String packageName, File dir) {
241 		File[] files = dir.listFiles();
242 		if (files != null) {
243 			boolean hasClassFiles = false;
244 			List<File> dirs = new ArrayList<>();
245 			for (File file : files) {
246 				if (file.isDirectory()) {
247 					dirs.add(file.getAbsoluteFile());
248 				} else if (!hasClassFiles) {
249 					if (file.getName().endsWith(Util.DOT_CLASS_SUFFIX)) {
250 						fPackages.put(packageName, dir.getAbsolutePath());
251 						hasClassFiles = true;
252 					}
253 				}
254 			}
255 			Iterator<File> iterator = dirs.iterator();
256 			while (iterator.hasNext()) {
257 				File child = iterator.next();
258 				String nextName = null;
259 				if (packageName.length() == 0) {
260 					nextName = child.getName();
261 				} else {
262 					nextName = packageName + "." + child.getName(); //$NON-NLS-1$
263 				}
264 				processDirectory(nextName, child);
265 			}
266 		}
267 	}
268 
269 	/**
270 	 * @see org.eclipse.pde.api.tools.internal.provisional.IApiTypeContainer#findClassFile(java.lang.String,
271 	 *      java.lang.String)
272 	 */
273 	@Override
findTypeRoot(String qualifiedName, String id)274 	public IApiTypeRoot findTypeRoot(String qualifiedName, String id) throws CoreException {
275 		return findTypeRoot(qualifiedName);
276 	}
277 
278 	@Override
getContainerType()279 	public int getContainerType() {
280 		return DIRECTORY;
281 	}
282 }
283