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