/*******************************************************************************
* Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.model.filesystems.impl;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IProject;
import org.jboss.tools.common.model.XModelObjectConstants;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.util.FileUtil;
public class JarAccess {
private String location = null;
private String templocation = null;
private ZipFile jar = null;
int jarLock = 0;
private Map<String,HashSet<String>> map = new HashMap<String,HashSet<String>>();
private Map<String,Long> fileEntries = new HashMap<String,Long>();
private boolean loading = false;
private boolean exists = false;
private long timeStamp = -1;
private long size = -1;
List<String> errors = new ArrayList<String>();
public JarAccess() {}
public List<String> getErrors() {
return errors;
}
public void setLocation(String location) {
this.location = location;
validate();
}
public synchronized void lockJar() {
jarLock++;
}
public String getLocation() {
return location;
}
public synchronized boolean isLoaded() {
return (exists || loading);
}
public synchronized void validate() {
if (isLoaded()) return;
loading = true;
templocation = null;
try {
int ind = location.indexOf(":/"); //$NON-NLS-1$
if (ind != 1 && ind != -1) {
int extI = location.lastIndexOf('.');
String ext = extI >= 0 ? location.substring(extI) : ".jar"; //$NON-NLS-1$
File f = File.createTempFile(ModelPlugin.TEMP_FILE_PREFIX, ext);
f.deleteOnExit();
InputStream i = new java.net.URL(location).openConnection().getInputStream();
FileOutputStream o = new FileOutputStream(f);
FileUtil.copy(i, o);
timeStamp = -1;
size = -1;
templocation = f.getCanonicalPath();
} else {
File nf = new File(location);
templocation = nf.getCanonicalPath();
timeStamp = nf.lastModified();
size = nf.length();
}
init();
exists = true;
} catch (IOException e) {
timeStamp = -1;
size = -1;
exists = false;
return;
} finally {
loading = false;
}
}
private void init() throws IOException {
ZipFile jar = getZipFile();
map.clear();
fileEntries.clear();
map.put("", new HashSet<String>()); //$NON-NLS-1$
try {
if(jar == null) return;
Enumeration<?> en = jar.entries();
while(en.hasMoreElements()) {
ZipEntry entry = (ZipEntry)en.nextElement();
String name = entry.getName();
if(name != null && name.endsWith(".class")) {
//Ignore .class entries. They are handled by JDT.
continue;
}
if(name != null && !name.endsWith(XModelObjectConstants.SEPARATOR) && entry.getSize() > 0) {
fileEntries.put(name, Long.valueOf(entry.getSize()));
}
register(name);
}
} finally {
unlockJar();
}
}
private ZipFile getZipFile() throws IOException {
synchronized (this) {
lockJar();
if(!new File(templocation).isFile()) return null;
if(jar != null) return jar;
return jar = new ZipFile(templocation);
}
}
public synchronized void unlockJar() {
jarLock--;
if(jarLock > 0 || jar == null) return;
if(jar != null && jarLock == 0) {
try {
jar.close();
} catch (IOException e) {
//ignore
} finally {
jar = null;
}
}
}
private void register(String path) {
String[] parsed = parse(path);
check(parsed[0]);
HashSet<String> set = map.get(parsed[0]);
if (!XModelObjectConstants.SEPARATOR.equals(parsed[1]))
set.add(parsed[1]);
}
private String[] parse(String path) {
String q = path;
if (path.endsWith(XModelObjectConstants.SEPARATOR))
q = q.substring(0, path.length() - 1);
int i = q.lastIndexOf('/');
String root = (i < 0) ? "" : path.substring(0, i); //$NON-NLS-1$
String name = (i < 0) ? path : path.substring(i + 1);
return new String[] { root, name };
}
private void check(String path) {
if (map.get(path) != null)
return;
map.put(path, new HashSet<String>());
String[] parsed = parse(path);
check(parsed[0]);
if ("".equals(parsed[1])) //$NON-NLS-1$
return;
HashSet<String> set = map.get(parsed[0]);
set.add(parsed[1] + XModelObjectConstants.SEPARATOR);
}
public synchronized String[] getChildren(String path) {
HashSet<String> set = map.get(path);
return (set == null) ? new String[0] : set.toArray(new String[0]);
}
public long getSize(String path) {
Long s = fileEntries.get(path);
return s == null ? 0 : s.longValue();
}
public String getContent(String path) {
int size = 1024;
byte b[] = new byte[size];
StringBuffer sb = new StringBuffer();
ZipFile jar = null;
try {
jar = getZipFile();
} catch (IOException e) {
String error = "JarAccess: cannot load zip file for location " + templocation; //$NON-NLS-1$
errors.add(error);
ModelPlugin.getDefault().logError(error);
}
if(jar == null) {
unlockJar();
return ""; //$NON-NLS-1$
}
int length = 0;
BufferedInputStream bs = null;
String encoding = null;
boolean first = true;
try {
ZipEntry entry = jar.getEntry(path);
if(entry == null && fileEntries.containsKey("/" + path)) {
entry = jar.getEntry("/" + path);
}
if(entry == null) {
String error = "JarAccess: cannot obtain entry for path '" + path + "' from jar '" + location + "'."; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
errors.add(error);
ModelPlugin.getDefault().logError(error);
return ""; //$NON-NLS-1$
}
InputStream is = jar.getInputStream(entry);
bs = new BufferedInputStream(is);
while ((length = bs.available()) > 0) {
if (length > size)
length = size;
length = bs.read(b, 0, length);
if (length < 0)
break;
if(first) {
first = false;
encoding = FileUtil.getEncoding(b);
}
if(encoding != null) {
sb.append(new String(b, 0, length, encoding));
} else {
sb.append(new String(b, 0, length));
}
}
return sb.toString();
} catch (IOException e) {
errors.add(e.getClass().getName() + " occurs when reading " + jar.getName() + " : " + e.getMessage()); //$NON-NLS-1$//$NON-NLS-2$
ModelPlugin.getPluginLog().logError("Exception occurs when reading " + jar.getName(), e); //$NON-NLS-1$
return ""; //$NON-NLS-1$
} finally {
unlockJar();
if(bs!=null) {
try {
bs.close();
} catch (IOException e) {
//ignore
}
}
}
}
public boolean isTextEntry(String path, int length) {
String b = getContent(path);
b = (b == null || b.length() < length) ? b : b.substring(length);
return FileUtil.isText(b);
}
synchronized boolean hasFolder(String path) {
return map.containsKey(path);
}
synchronized boolean hasFile(String path) {
if (path == null)
return false;
int i = path.lastIndexOf('/');
String p = (i < 0) ? "" : path.substring(0, i); //$NON-NLS-1$
String n = path.substring(i + 1);
Set<String> set = map.get(p);
return set != null && set.contains(n);
}
public LFileObject getFileObject(String alias, String relpath) {
return new LFileObjectJarImpl(this, alias, relpath);
}
public boolean isModified() {
if (timeStamp == -1) {
return true;
}
File f = new File(location);
return (timeStamp != f.lastModified() || size != f.length());
}
public synchronized void invalidate() {
exists = false;
map.clear();
timeStamp = -1;
size = -1;
}
public String getTempLocation() {
return templocation;
}
JarSystemImpl main = null;
Map<IProject, JarSystemImpl> slaves = new HashMap<IProject, JarSystemImpl>();
public JarSystemImpl getMain() {
IProject p = EclipseResourceUtil.getProject(main);
if(p == null || !p.isAccessible() || !main.isActive()) {
main = null;
synchronized(slaves) {
Iterator<Map.Entry<IProject, JarSystemImpl>> it = slaves.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<IProject, JarSystemImpl> s = it.next();
p = s.getKey();
if(p == null || !p.isAccessible() || !s.getValue().isActive()) {
it.remove();
} else if(main == null) {
main = s.getValue();
it.remove();
}
}
}
if(main != null) main.jarUpdated();
JarSystemImpl[] ss = getSlaves();
for (JarSystemImpl s: ss) s.jarUpdated();
}
return main;
}
public void setMain(JarSystemImpl main) {
this.main = main;
}
public JarSystemImpl[] getSlaves() {
synchronized(slaves) {
return slaves.values().toArray(new JarSystemImpl[slaves.size()]);
}
}
public void addSlave(JarSystemImpl s) {
if(main == null) {
main = s;
} else {
synchronized(slaves) {
IProject p = EclipseResourceUtil.getProject(s);
if(p != null) {
slaves.put(p, s);
}
}
}
}
void onProjectDelete(IProject project) {
slaves.remove(project);
}
public boolean isSlave(JarSystemImpl s) {
return slaves.containsValue(s);
}
}
class LFileObjectJarImpl implements LFileObject {
private JarAccess access = null;
private String aliaspath = null;
private String relpath = null;
public LFileObjectJarImpl(JarAccess access, String alias, String relpath) {
this.access = access;
aliaspath = relpath.length() == 0 ? alias : alias + '/' + relpath;
this.relpath = relpath;
}
public String getName() {
return relpath.substring(relpath.lastIndexOf('/') + 1);
}
public boolean exists() {
return access.hasFolder(relpath) || access.hasFile(relpath);
}
public boolean isDirectory() {
return access.hasFolder(relpath);
}
public boolean isFile() {
return access.hasFile(relpath);
}
public long lastModified() {
return 0;
}
public String getPath() {
return aliaspath;
}
public boolean canWrite() {
return false;
}
public String read() {
return access.getContent(relpath);
}
public void write(String s) {
}
public String[] listFiles() {
String[] r = access.getChildren(relpath);
String rp = getPath();
for (int i = 0; i < r.length; i++) {
if (r[i].endsWith(XModelObjectConstants.SEPARATOR))
r[i] = r[i].substring(0, r[i].length() - 1);
r[i] = rp + XModelObjectConstants.SEPARATOR + r[i];
}
return r;
}
public boolean mkdirs() {
return false;
}
public boolean delete() {
return false;
}
}