Initial commit of Sigil contribution. (FELIX-1142)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793581 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/org.cauldron.sigil.search/.classpath b/sigil/org.cauldron.sigil.search/.classpath
new file mode 100644
index 0000000..254ffb7
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry exported="true" kind="lib" path="lib/bcel-5.2.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sigil/org.cauldron.sigil.search/.project b/sigil/org.cauldron.sigil.search/.project
new file mode 100644
index 0000000..3e37565
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.cauldron.sigil.search</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sigil/org.cauldron.sigil.search/.settings/org.eclipse.jdt.core.prefs b/sigil/org.cauldron.sigil.search/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..636afaf
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Fri Oct 03 18:13:19 PDT 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/sigil/org.cauldron.sigil.search/META-INF/MANIFEST.MF b/sigil/org.cauldron.sigil.search/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..7a97ca1
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/META-INF/MANIFEST.MF
@@ -0,0 +1,17 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Search Plug-in
+Bundle-SymbolicName: org.cauldron.sigil.search;singleton:=true
+Bundle-Version: 0.8.0.qualifier
+Bundle-Activator: org.cauldron.sigil.search.SigilSearch
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.jdt.core;bundle-version="3.4.0",
+ org.cauldron.sigil.core;bundle-version="0.7.0",
+ org.cauldron.bld.core;bundle-version="0.7.0",
+ org.eclipse.core.resources;bundle-version="3.4.0"
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Export-Package: org.cauldron.sigil.search
+Bundle-ClassPath: lib/bcel-5.2.jar,
+ .
diff --git a/sigil/org.cauldron.sigil.search/build.properties b/sigil/org.cauldron.sigil.search/build.properties
new file mode 100644
index 0000000..ef5e456
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               lib/bcel-5.2.jar
diff --git a/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/ISearchResult.java b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/ISearchResult.java
new file mode 100644
index 0000000..f47deed
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/ISearchResult.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.search;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+
+public interface ISearchResult {
+	ISigilBundle getProvider();
+	IPackageExport getExport();
+	String getPackageName();
+	String getClassName();
+}
diff --git a/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/SigilSearch.java b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/SigilSearch.java
new file mode 100644
index 0000000..79ca623
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/SigilSearch.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.search;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+
+import org.apache.bcel.classfile.ClassParser;
+import org.apache.bcel.classfile.JavaClass;
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryChangeListener;
+import org.cauldron.sigil.repository.IRepositoryVisitor;
+import org.cauldron.sigil.repository.RepositoryChangeEvent;
+import org.cauldron.sigil.search.index.Index;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class SigilSearch extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.cauldron.sigil.search";
+
+	private static final String CLASS_EXTENSION = ".class";
+
+	// The shared instance
+	private static SigilSearch plugin;
+	private static Index index;
+
+	/**
+	 * The constructor
+	 */
+	public SigilSearch() {
+	}
+	
+	public static List<ISearchResult> findProviders(String fullyQualifiedName, ISigilProjectModel sigil, IProgressMonitor monitor) {
+		listen(sigil);
+		return index.findProviders(fullyQualifiedName, monitor);
+	}
+	
+	public static List<ISearchResult> findProviders(Pattern namePattern, ISigilProjectModel sigil, IProgressMonitor monitor) {
+		listen(sigil);
+		return index.findProviders(namePattern, monitor);
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static SigilSearch getDefault() {
+		return plugin;
+	}
+	
+	private static void listen(ISigilProjectModel sigil) {
+		synchronized(plugin) {
+			if ( index == null ) {
+				index = new Index();
+				for ( IBundleRepository rep : SigilCore.getRepositoryManager(sigil).getRepositories() ) {
+					index(index, rep);
+				}
+				
+				SigilCore.getRepositoryManager(sigil).addRepositoryChangeListener( new IRepositoryChangeListener() {
+					public void repositoryChanged(RepositoryChangeEvent event) {
+						index(index, event.getRepository());
+					}					
+				});
+			}
+		}
+	}
+
+	private static void index(final Index index, final IBundleRepository rep) {
+		index.delete(rep);
+		rep.accept( new IRepositoryVisitor() {
+			public boolean visit(ISigilBundle bundle) {
+				ISigilProjectModel p = bundle.getAncestor(ISigilProjectModel.class);
+				if ( p == null ) {
+					if ( bundle.isSynchronized() ) {
+						IPath loc = bundle.getLocation();
+						if ( loc.isAbsolute() ) {
+							indexJar(rep, bundle, loc);
+						}
+					}
+				}
+				else {
+					indexProject(rep, p);
+				}
+				return true;
+			}					
+		});
+	}
+	
+	private static void indexProject(IBundleRepository rep, ISigilProjectModel sigil) {
+		try {
+			for (ICompilationUnit unit : JavaHelper.findCompilationUnits(sigil) ) {
+				IPackageFragment p = (IPackageFragment) unit.getParent();
+				ISigilBundle b = sigil.getBundle();
+				IPackageExport export = b.findExport(p.getElementName());
+				index.addEntry(unit, rep, b, export != null);
+			}
+		} catch (JavaModelException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	private static void indexJar(IBundleRepository rep, ISigilBundle bundle, IPath loc) {
+		JarFile jar = null;
+		try {
+			jar = new JarFile(loc.toOSString());
+			for (Map.Entry<JarEntry, IPackageExport> export : findExportedClasses(bundle, jar).entrySet() ) {
+				JarEntry entry = export.getKey();
+				InputStream in = null;
+				try {
+					in = jar.getInputStream(entry);
+					ClassParser parser = new ClassParser(in, entry.getName());
+					JavaClass c = parser.parse();
+					index.addEntry(c, rep, bundle, true);
+				}
+				finally {
+					if ( in != null ) {
+						in.close();
+					}
+				}
+			}
+		}
+		catch (IOException e) {
+			SigilCore.error( "Failed to read jar " + loc, e );
+		}
+		finally {
+			if ( jar != null ) {
+				try {
+					jar.close();
+				} catch (IOException e) {
+					SigilCore.error( "Failed to close jar " + loc, e );
+				}
+			}
+		}
+	}
+
+	private static Map<JarEntry, IPackageExport> findExportedClasses(ISigilBundle bundle, JarFile jar) {
+		HashMap<JarEntry, IPackageExport> found = new HashMap<JarEntry, IPackageExport>();
+		
+		IPackageExport[] exports = bundle.getBundleInfo().childrenOfType(IPackageExport.class);
+		if ( exports.length > 0 ) {
+			Arrays.sort(exports, new Comparator<IPackageExport> () {
+				public int compare(IPackageExport o1, IPackageExport o2) {
+					return -1 * o1.compareTo(o2);
+				}				
+			});
+			for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
+				JarEntry entry = e.nextElement();
+				String className = toClassName(entry);
+				if ( className != null ) {
+					IPackageExport ex = findExport(className, exports);
+					
+					if ( found != null ) {
+						found.put( entry, ex );
+					}
+				}
+			}
+		}
+		
+		return found;
+	}
+
+	private static IPackageExport findExport(String className, IPackageExport[] exports) {
+		for ( IPackageExport e : exports ) {
+			if ( className.startsWith(e.getPackageName())) {
+				return e;
+			}
+		}
+		return null;
+	}
+
+	private static String toClassName(JarEntry entry) {
+		String name = entry.getName();
+		if ( name.endsWith(CLASS_EXTENSION) ) {
+			name = name.substring(0, name.length() - CLASS_EXTENSION.length());
+			name = name.replace('/', '.');
+			return name;
+		}
+		else {
+			return null;
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/index/Index.java b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/index/Index.java
new file mode 100644
index 0000000..b62421f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.search/src/org/cauldron/sigil/search/index/Index.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.cauldron.sigil.search.index;
+
+import java.lang.ref.SoftReference;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Pattern;
+
+import org.apache.bcel.classfile.JavaClass;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.search.ISearchResult;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.osgi.framework.Version;
+
+public class Index {
+	private HashMap<String, ClassData> primary = new HashMap<String, ClassData>();
+	private HashMap<IBundleRepository, HashSet<String>> secondary = new HashMap<IBundleRepository, HashSet<String>>();
+	
+	private final ReadWriteLock lock = new ReentrantReadWriteLock();
+	
+	static class ClassData {
+		HashMap<IBundleRepository, Set<ISearchResult>> provided = new HashMap<IBundleRepository, Set<ISearchResult>>();
+
+		void add(IBundleRepository rep, ISearchResult export) {
+			Set<ISearchResult> exports = provided.get(rep);
+			
+			if ( exports == null ) {
+				exports = new HashSet<ISearchResult>();
+				provided.put( rep, exports );
+			}
+			
+			exports.add(export);
+		}
+
+		List<ISearchResult> getResults() {
+			LinkedList<ISearchResult> exports = new LinkedList<ISearchResult>();
+			for ( Set<ISearchResult> p : provided.values() ) {
+				exports.addAll(p);
+			}
+			return exports;
+		}
+
+		void remove(IBundleRepository rep) {
+			provided.remove(rep);
+		}
+		
+		boolean isEmpty() {
+			return provided.isEmpty();
+		}
+	}
+	
+	static class SearchResult implements ISearchResult {
+		private final String className;
+		private final String packageName;
+		private final IBundleRepository rep;
+		private final String bundleSymbolicName;
+		private final Version version;
+		private final boolean exported;
+		
+		private SoftReference<ISigilBundle> bundleReference;
+		private SoftReference<IPackageExport> exportReference;
+		
+		public SearchResult(String className, IBundleRepository rep, ISigilBundle bundle, String packageName, boolean exported) {
+			this.className = className;
+			this.rep = rep;
+			this.exported = exported;
+			this.bundleSymbolicName = bundle.getBundleInfo().getSymbolicName();
+			this.version = bundle.getVersion();
+			this.packageName = packageName;
+		}
+
+		public String getClassName() {
+			return className;
+		}
+		
+		public String getPackageName() {
+			return packageName;
+		}
+
+		public IPackageExport getExport() {
+			IPackageExport ipe = null;
+			if ( exported ) {
+				ipe = exportReference == null ? null : exportReference.get();
+				if (ipe == null) {
+					ipe = getProvider().findExport(packageName);
+					exportReference = new SoftReference<IPackageExport>(ipe);
+				}
+			}
+			return ipe; 
+		}
+
+		public ISigilBundle getProvider() {
+			ISigilBundle b = bundleReference == null ? null : bundleReference.get();
+			if ( b == null ) {
+				IRequiredBundle rb = ModelElementFactory.getInstance().newModelElement(IRequiredBundle.class);
+				rb.setSymbolicName(bundleSymbolicName);
+				VersionRange versions = new VersionRange(false, version, version, false);
+				rb.setVersions(versions);
+				b = rep.findProvider(rb, 0);
+				bundleReference = new SoftReference<ISigilBundle>(b);
+			}
+			return b;
+		}
+		
+	}
+
+	public void addEntry(JavaClass c, IBundleRepository rep, ISigilBundle bundle, boolean exported) {
+		addEntry(c.getClassName(), rep, bundle, c.getPackageName(), exported);
+	}
+	
+	public void addEntry(ICompilationUnit unit, IBundleRepository rep, ISigilBundle bundle, boolean exported) {
+		String name = unit.getElementName();
+		if ( name.endsWith( ".java" ) ) {
+			name = name.substring(0, name.length() - 5 );
+		}
+		IPackageFragment p = (IPackageFragment) unit.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
+		addEntry(p.getElementName() + "." + name, rep, bundle, p.getElementName(), exported);
+	}
+
+	private void addEntry(String className, IBundleRepository rep, ISigilBundle bundle, String packageName, boolean exported) {
+		List<String> keys = genKeys(className);
+		lock.writeLock().lock();
+		try {
+			for ( String key : keys ) {
+				ClassData data = primary.get(key);
+				
+				if ( data == null ) {
+					data = new ClassData();
+					primary.put(key, data);
+				}
+				
+				SearchResult result = new SearchResult(className, rep, bundle, packageName, exported);
+				data.add(rep, result);
+			}
+			
+			HashSet<String> all = secondary.get(rep);
+			if ( all == null ) {
+				all = new HashSet<String>();
+				secondary.put(rep, all);
+			}
+			all.addAll(keys);
+		}
+		finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	
+	public List<ISearchResult> findProviders(String className, IProgressMonitor monitor) {
+		lock.readLock().lock();
+		try {
+			ClassData data = primary.get(className);
+			return data == null ? Collections.<ISearchResult>emptyList() : data.getResults();
+		}
+		finally {
+			lock.readLock().unlock();
+		}
+	}	
+	
+	public List<ISearchResult> findProviders(Pattern className, IProgressMonitor monitor) {
+		lock.readLock().lock();
+		try {
+			ClassData data = primary.get(className);
+			return data == null ? Collections.<ISearchResult>emptyList() : data.getResults();
+		}
+		finally {
+			lock.readLock().unlock();
+		}
+	}
+	
+	public void delete(IBundleRepository rep) {
+		lock.writeLock().lock();
+		try {
+			Set<String> keys = secondary.remove(rep);
+			if ( keys != null ) {
+				for ( String key : keys ) {
+					ClassData data = primary.get(key);
+					data.remove(rep);
+					if ( data.isEmpty() ) {
+						primary.remove(key);
+					}
+				}
+			}
+		}
+		finally {
+			lock.writeLock().unlock();
+		}
+	}
+	
+	private List<String> genKeys(String className) {
+		LinkedList<String> keys = new LinkedList<String>();
+		keys.add(className);
+		int i = className.lastIndexOf('.');
+		if ( i != -1 ) {
+			String name = className.substring(i + 1);
+			keys.add( name );
+		}
+		return keys;
+	}
+
+}