| /******************************************************************************* |
| * Copyright (c) 2016, 2017 xored software, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are 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: |
| * xored software, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.dltk.core.caching; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.zip.CRC32; |
| |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.dltk.compiler.util.Util; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.RuntimePerformanceMonitor; |
| import org.eclipse.dltk.core.RuntimePerformanceMonitor.PerformanceNode; |
| import org.eclipse.dltk.core.caching.cache.CacheEntry; |
| import org.eclipse.dltk.core.caching.cache.CacheEntryAttribute; |
| import org.eclipse.dltk.core.caching.cache.CacheFactory; |
| import org.eclipse.dltk.core.caching.cache.CacheIndex; |
| import org.eclipse.dltk.core.environment.EnvironmentManager; |
| import org.eclipse.dltk.core.environment.IEnvironment; |
| import org.eclipse.dltk.core.environment.IFileHandle; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl; |
| |
| /** |
| * This class is designed to store any kind of information into metadata cache. |
| */ |
| public class MetadataContentCache extends AbstractContentCache { |
| private static final int DAY_IN_MILIS = 60;// 1000 * 60 * 60 * 24; |
| private static final int SAVE_DELTA = 1000 * 60; // Minute |
| private Resource indexResource = null; |
| private long newSaveTime = 0; |
| |
| private static class EntryKey { |
| private String environment; |
| private String path; |
| |
| public EntryKey(String environment, String path) { |
| this.environment = environment; |
| this.path = path; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result |
| + ((environment == null) ? 0 : environment.hashCode()); |
| result = prime * result + ((path == null) ? 0 : path.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| EntryKey other = (EntryKey) obj; |
| if (environment == null) { |
| if (other.environment != null) |
| return false; |
| } else if (!environment.equals(other.environment)) |
| return false; |
| if (path == null) { |
| if (other.path != null) |
| return false; |
| } else if (!path.equals(other.path)) |
| return false; |
| return true; |
| } |
| } |
| |
| private Map<EntryKey, CacheEntry> entryCache = new HashMap<>(); |
| private IPath cacheLocation; |
| private CRC32 checksum = new CRC32(); |
| |
| public MetadataContentCache(IPath cacheLocation) { |
| this.cacheLocation = cacheLocation; |
| } |
| |
| private synchronized void initialize() { |
| if (indexResource == null) { |
| File file = new File(cacheLocation.toOSString()); |
| if (!file.exists()) { |
| file.mkdir(); |
| } |
| |
| IPath indexFile = cacheLocation.append("index"); |
| indexResource = new XMIResourceImpl(); |
| indexFileHandle = new File(indexFile.toOSString()); |
| if (indexFileHandle.exists()) { |
| try { |
| BufferedInputStream loadStream = new BufferedInputStream( |
| new FileInputStream(indexFileHandle), 4096); |
| indexResource.load(loadStream, null); |
| loadStream.close(); |
| } catch (Exception e) { |
| save(false); |
| if (DLTKCore.DEBUG) { |
| // e.printStackTrace(); |
| } |
| } |
| } |
| EList<EObject> contents = indexResource.getContents(); |
| for (EObject eObject : contents) { |
| CacheIndex index = (CacheIndex) eObject; |
| // String to entry cache. |
| EList<CacheEntry> entries = index.getEntries(); |
| for (CacheEntry cacheEntry : entries) { |
| entryCache.put(makeKey(cacheEntry), cacheEntry); |
| } |
| } |
| } |
| } |
| |
| private synchronized CacheEntry getEntry(IFileHandle handle) { |
| initialize(); |
| EntryKey key = makeKey(handle); |
| if (entryCache.containsKey(key)) { |
| CacheEntry entry = entryCache.get(key); |
| long accessTime = entry.getLastAccessTime(); |
| long timeMillis = System.currentTimeMillis(); |
| if (timeMillis - accessTime > DAY_IN_MILIS) { |
| long entryTimestamp = entry.getTimestamp() / 1000; |
| long handleTimestamp = getHandleLastModification(handle) / 1000; |
| if (entryTimestamp == handleTimestamp) { |
| entry.setLastAccessTime(timeMillis); |
| return entry; |
| } else { |
| entry.setLastAccessTime(timeMillis); |
| removeCacheEntry(entry, key); |
| } |
| } else { |
| entry.setLastAccessTime(timeMillis); |
| return entry; |
| } |
| } |
| CacheIndex index = getCacheIndex(handle.getEnvironmentId()); |
| CacheEntry entry = CacheFactory.eINSTANCE.createCacheEntry(); |
| entry.setPath(handle.getPath().toPortableString()); |
| entry.setTimestamp(getHandleLastModification(handle)); |
| index.getEntries().add(entry); |
| entry.setLastAccessTime(System.currentTimeMillis()); |
| entryCache.put(key, entry); |
| return entry; |
| } |
| |
| private long getHandleLastModification(IFileHandle handle) { |
| final IEnvironment environment = handle.getEnvironment(); |
| if (environment != null && environment.isLocal()) { |
| try { |
| File file = new File(handle.getPath().toOSString()); |
| File canonicalFile = file.getCanonicalFile(); |
| if (!file.getAbsolutePath() |
| .equals(canonicalFile.getAbsoluteFile())) { |
| return canonicalFile.lastModified(); |
| } |
| } catch (IOException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| return handle.lastModified(); |
| } |
| |
| private CacheIndex getCacheIndex(String environmentId) { |
| EList<EObject> contents = indexResource.getContents(); |
| for (EObject eObject : contents) { |
| CacheIndex index = (CacheIndex) eObject; |
| if (index.getEnvironment().equals(environmentId)) { |
| return index; |
| } |
| } |
| CacheIndex index = CacheFactory.eINSTANCE.createCacheIndex(); |
| index.setEnvironment(environmentId); |
| index.setLastIndex(0); |
| contents.add(index); |
| return index; |
| } |
| |
| private void removeCacheEntry(CacheEntry entry, EntryKey key) { |
| if (entry == null || key == null) { |
| return; |
| } |
| // We need to remove old files |
| EList<CacheEntryAttribute> attributes = entry.getAttributes(); |
| for (CacheEntryAttribute attr : attributes) { |
| removeAttribute(attr); |
| } |
| attributes.clear(); |
| CacheIndex index = (CacheIndex) entry.eContainer(); |
| index.getEntries().remove(entry); |
| entryCache.remove(key); |
| } |
| |
| private void removeAttribute(CacheEntryAttribute attr) { |
| String location = attr.getLocation(); |
| IPath cacheEntryFile = cacheLocation.append(location); |
| File file = new File(cacheEntryFile.toOSString()); |
| if (file.exists()) { |
| file.delete(); |
| } |
| } |
| |
| private EntryKey makeKey(CacheEntry entry) { |
| CacheIndex index = (CacheIndex) entry.eContainer(); |
| return new EntryKey(index.getEnvironment(), entry.getPath()); |
| } |
| |
| private EntryKey makeKey(IFileHandle handle) { |
| return new EntryKey(handle.getEnvironmentId(), |
| handle.getPath().toString()); |
| } |
| |
| long changeCount = 0; |
| private File indexFileHandle; |
| |
| public synchronized void save(boolean countSaves) { |
| if (indexResource == null) { |
| return; |
| } |
| if (countSaves) { |
| long current = System.currentTimeMillis(); |
| if (current > newSaveTime) { |
| newSaveTime = current + SAVE_DELTA; |
| } else { |
| return; |
| } |
| } |
| try { |
| Map<String, Object> options = new HashMap<>(); |
| options.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Boolean.TRUE); |
| BufferedOutputStream outStream = new BufferedOutputStream( |
| new FileOutputStream(indexFileHandle), 4096); |
| indexResource.save(outStream, options); |
| outStream.close(); |
| } catch (IOException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| @Override |
| public InputStream getCacheEntryAttribute(IFileHandle handle, |
| String attribute) { |
| if (handle == null) { |
| return null; |
| } |
| File file = null; |
| CacheEntry entry = null; |
| synchronized (this) { |
| entry = getEntry(handle); |
| EList<CacheEntryAttribute> attributes = entry.getAttributes(); |
| for (CacheEntryAttribute cacheEntryAttribute : attributes) { |
| if (cacheEntryAttribute.getName().equals(attribute)) { |
| file = new File(cacheLocation |
| .append(cacheEntryAttribute.getLocation()) |
| .toOSString()); |
| break; |
| } |
| } |
| } |
| if (file != null && file.exists()) { |
| try { |
| PerformanceNode node = RuntimePerformanceMonitor.begin(); |
| ByteArrayOutputStream bout = new ByteArrayOutputStream(); |
| BufferedInputStream inp = new BufferedInputStream( |
| new FileInputStream(file), 4096); |
| Util.copy(inp, bout); |
| inp.close(); |
| node.done("Metadata", RuntimePerformanceMonitor.IOREAD, |
| file.length(), |
| EnvironmentManager.getLocalEnvironment()); |
| return new ByteArrayInputStream(bout.toByteArray()); |
| } catch (FileNotFoundException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| return null; |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public synchronized OutputStream getCacheEntryAttributeOutputStream( |
| IFileHandle handle, String attribute) { |
| File file = getEntryAsFile(handle, attribute); |
| try { |
| return new BufferedOutputStream(new FileOutputStream(file), 4096); |
| } catch (FileNotFoundException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public File getEntryAsFile(IFileHandle handle, String attribute) { |
| if (handle == null) { |
| return null; |
| } |
| CacheEntry entry = getEntry(handle); |
| File file = null; |
| EList<CacheEntryAttribute> attributes = entry.getAttributes(); |
| for (CacheEntryAttribute cacheEntryAttribute : attributes) { |
| if (cacheEntryAttribute.getName().equals(attribute)) { |
| file = new File( |
| cacheLocation.append(cacheEntryAttribute.getLocation()) |
| .toOSString()); |
| return file; |
| } |
| } |
| |
| IPath location = generateNewLocation(handle.getPath(), |
| handle.getEnvironmentId()); |
| CacheEntryAttribute attrEntry = CacheFactory.eINSTANCE |
| .createCacheEntryAttribute(); |
| attrEntry.setLocation(location.toPortableString()); |
| attrEntry.setName(attribute); |
| entry.getAttributes().add(attrEntry); |
| save(true); |
| file = new File(cacheLocation.append(location).toOSString()); |
| return file; |
| } |
| |
| private IPath generateNewLocation(IPath path, String environment) { |
| checksum.reset(); |
| checksum.update(environment.getBytes()); |
| IPath indexPath = cacheLocation |
| .append(Long.toString(checksum.getValue())); |
| File indexFolderFile = new File(indexPath.toOSString()); |
| if (!indexFolderFile.exists()) { |
| indexFolderFile.mkdir(); |
| } |
| checksum.reset(); |
| checksum.update( |
| path.removeLastSegments(1).toPortableString().getBytes()); |
| IPath folder = indexPath.append(Long.toString(checksum.getValue())); |
| File folderFile = new File(folder.toOSString()); |
| if (!folderFile.exists()) { |
| folderFile.mkdir(); |
| } |
| CacheIndex index = getCacheIndex(environment); |
| IPath location = null; |
| long i = index.getLastIndex() + 1; |
| while (true) { |
| location = folder.append(Long.toString(i++) + ".idx"); |
| File file = new File(location.toOSString()); |
| if (!file.exists()) { |
| index.setLastIndex(i); |
| return location |
| .removeFirstSegments(cacheLocation.segmentCount()) |
| .setDevice(null); |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void removeCacheEntryAttributes(IFileHandle handle, |
| String attribute) { |
| if (handle == null) { |
| return; |
| } |
| CacheEntry entry = getEntry(handle); |
| EList<CacheEntryAttribute> attributes = entry.getAttributes(); |
| for (CacheEntryAttribute cacheEntryAttribute : attributes) { |
| if (cacheEntryAttribute.getName().equals(attribute)) { |
| removeAttribute(cacheEntryAttribute); |
| attributes.remove(cacheEntryAttribute); |
| save(true); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void clearCacheEntryAttributes(IFileHandle handle) { |
| if (handle == null) { |
| return; |
| } |
| EntryKey key = makeKey(handle); |
| if (entryCache.containsKey(key)) { |
| CacheEntry entry = entryCache.get(key); |
| removeCacheEntry(entry, key); |
| save(true); |
| } |
| } |
| |
| @Override |
| public synchronized void clear() { |
| initialize(); |
| Set<EntryKey> keySet = new HashSet<>(entryCache.keySet()); |
| for (EntryKey k : keySet) { |
| removeCacheEntry(entryCache.get(k), k); |
| } |
| save(true); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| @Override |
| public InputStream getCacheEntryAttribute(IFileHandle handle, |
| String attribute, boolean localonly) { |
| return getCacheEntryAttribute(handle, attribute); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| @Override |
| public void updateFolderTimestamps(IFileHandle parent) { |
| IFileHandle[] children = parent.getChildren(); |
| if (children == null) { |
| return; |
| } |
| for (IFileHandle child : children) { |
| getEntry(child); |
| } |
| |
| } |
| } |