/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.internal.protocols;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Karelun Huang
*/
public class FilePathParser {
public static final String URI_SCHEME = "file"; //$NON-NLS-1$
private static final String FILE_PROTOCOL = URI_SCHEME + ":"; //$NON-NLS-1$
private static final String FILE_SEP = System.getProperty("file.separator"); //$NON-NLS-1$
private static final String PROTOCOL_SEP = "//"; //$NON-NLS-1$
private static final String PATH_SEP = "/"; //$NON-NLS-1$
private static final String PARENT_DIR = ".."; //$NON-NLS-1$
private static final String SAME_DIR = "."; //$NON-NLS-1$
private static final String PARENT_DIR_SEP = PARENT_DIR + FILE_SEP;
private static final String SAME_DIR_SEP = SAME_DIR + FILE_SEP;
public static final String ABSTRACT_FILE_BASE = System
.getProperty("user.home"); //$NON-NLS-1$
private static final String WIN_NETWORK_PATH_PREFIX = "\\\\"; //$NON-NLS-1$
static Boolean IS_WINDOWS = null;
public static boolean isFileURI(String uri) {
return uri != null && uri.startsWith(FILE_PROTOCOL);
}
public static String toPath(String uri) {
if (uri == null)
return null;
try {
uri = decode(uri, true);
} catch (Exception e) {
}
String path;
if (uri.startsWith(FILE_PROTOCOL))
path = uri.substring(FILE_PROTOCOL.length());
else
path = uri;
if (path.startsWith(PROTOCOL_SEP))
path = path.substring(2);
if (isWindows()) {
path = path.replaceAll(PATH_SEP, WIN_NETWORK_PATH_PREFIX);
}
return path;
}
public static String toURI(String path, boolean relative) {
if (path == null)
return null;
if (isWindows()) {
path = path.replaceAll(WIN_NETWORK_PATH_PREFIX, PATH_SEP);
}
return encode(relative ? FILE_PROTOCOL + path
: FILE_PROTOCOL + PROTOCOL_SEP + path, true);
}
public static boolean isPathRelative(String path) {
return !(isWindows() && path.startsWith(WIN_NETWORK_PATH_PREFIX))
&& !new File(path).isAbsolute();
}
private static List<String> calculateRoutine(File file) {
ArrayList<String> routine = new ArrayList<String>();
String name;
while (file != null) {
name = file.getName();
if (name == null || "".equals(name)) { //$NON-NLS-1$
// A root directory may have no name, so we add its whole path:
name = file.getPath();
}
routine.add(0, name);
file = file.getParentFile();
}
return routine;
}
public static String toRelativePath(String base, String absolutePath) {
if (absolutePath.equals(base))
return SAME_DIR;
List<String> baseRoutine = calculateRoutine(new File(base));
List<String> absRoutine = calculateRoutine(new File(absolutePath));
// Calculate the segment number of the common root:
int start = 0;
while (start < absRoutine.size() && start < baseRoutine.size()
&& absRoutine.get(start).equals(baseRoutine.get(start))) {
start++;
}
if (start == absRoutine.size())
// Absolute path equals base:
return SAME_DIR;
StringBuilder builder = new StringBuilder();
if (start < baseRoutine.size()) {
for (int i = start; i < baseRoutine.size(); i++) {
builder.append(PARENT_DIR);
builder.append(FILE_SEP);
}
}
for (int i = start; i < absRoutine.size(); i++) {
builder.append(absRoutine.get(i));
if (i < absRoutine.size() - 1) {
builder.append(FILE_SEP);
}
}
return builder.toString();
// File file = new File(absolutePath);
// List<File> routine = new ArrayList<File>();
// routine = getRoutine(file, routine);
//
// File baseFile = new File(base);
// List<File> baseRoutine = new ArrayList<File>();
// baseRoutine.add(baseFile);
// baseRoutine = getRoutine(baseFile, baseRoutine);
// int start = findStart(routine, baseRoutine);
// StringBuilder sb = new StringBuilder(20);
// String sep = SEP;
// for (int i = start; i < baseRoutine.size(); i++) {
// sb.append(".."); //$NON-NLS-1$
// sb.append(sep);
// }
// if (start == 0 && isWindows && !absolutePath.startsWith(SEP)) {
// return sb.toString() + new File(absolutePath).getAbsolutePath();
// } else {
// for (int i = start; i < routine.size(); i++) {
// sb.append(routine.get(i).getName());
// sb.append(sep);
// }
// sb.append(file.getName());
// }
// return sb.toString();
}
public static String toAbsolutePath(String base, String relativePath) {
File file = new File(base);
while (!"".equals(relativePath)) { //$NON-NLS-1$
if (isWindows()) {
if (relativePath.startsWith(WIN_NETWORK_PATH_PREFIX))
return relativePath.substring(FILE_SEP.length());
}
if (relativePath.startsWith(PARENT_DIR_SEP)) {
if (file != null)
file = file.getParentFile();
relativePath = relativePath.substring(PARENT_DIR_SEP.length());
} else if (relativePath.startsWith(SAME_DIR_SEP)) {
relativePath = relativePath.substring(SAME_DIR_SEP.length());
} else {
int sepIndex = relativePath.indexOf(FILE_SEP);
if (sepIndex < 0) {
if (file == null)
return relativePath;
return new File(file, relativePath).getPath();
} else {
if (file == null) {
file = new File(relativePath.substring(0, sepIndex));
} else {
file = new File(file,
relativePath.substring(0, sepIndex));
}
relativePath = relativePath
.substring(sepIndex + FILE_SEP.length());
}
}
}
if (file == null)
return ""; //$NON-NLS-1$
return file.getPath();
// try {
// return new File(base, relativePath).getCanonicalPath();
// } catch (IOException e) {
// return new File(base, relativePath).getAbsolutePath();
// }
}
/*
* ECMA 3, 15.1.3 URI Handling Function Properties The following are
* implementations of the algorithms given in the ECMA specification for the
* hidden functions 'Encode' and 'Decode'.
*/
private static String encode(String str, boolean fullUri) {
byte[] utf8buf = null;
StringBuffer sb = null;
for (int k = 0, length = str.length(); k != length; ++k) {
char C = str.charAt(k);
if (encodeUnescaped(C, fullUri)) {
if (sb != null) {
sb.append(C);
}
} else {
if (sb == null) {
sb = new StringBuffer(length + 3);
sb.append(str);
sb.setLength(k);
utf8buf = new byte[6];
}
if (0xDC00 <= C && C <= 0xDFFF) {
throw new IllegalArgumentException();
}
int V;
if (C < 0xD800 || 0xDBFF < C) {
V = C;
} else {
k++;
if (k == length) {
throw new IllegalArgumentException();
}
char C2 = str.charAt(k);
if (!(0xDC00 <= C2 && C2 <= 0xDFFF)) {
throw new IllegalArgumentException();
}
V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000;
}
int L = oneUcs4ToUtf8Char(utf8buf, V);
for (int j = 0; j < L; j++) {
int d = 0xff & utf8buf[j];
sb.append('%');
sb.append(toHexChar(d >>> 4));
sb.append(toHexChar(d & 0xf));
}
}
}
return (sb == null) ? str : sb.toString();
}
private static String decode(String str, boolean fullUri) {
char[] buf = null;
int bufTop = 0;
for (int k = 0, length = str.length(); k != length;) {
char C = str.charAt(k);
if (C != '%') {
if (buf != null) {
buf[bufTop++] = C;
}
++k;
} else {
if (buf == null) {
// decode always compress so result can not be bigger then
// str.length()
buf = new char[length];
str.getChars(0, k, buf, 0);
bufTop = k;
}
int start = k;
if (k + 3 > length)
throw new IllegalArgumentException();
int B = unHex(str.charAt(k + 1), str.charAt(k + 2));
if (B < 0)
throw new IllegalArgumentException();
k += 3;
if ((B & 0x80) == 0) {
C = (char) B;
} else {
// Decode UTF-8 sequence into ucs4Char and encode it into
// UTF-16
int utf8Tail, ucs4Char, minUcs4Char;
if ((B & 0xC0) == 0x80) {
// First UTF-8 should be ouside 0x80..0xBF
throw new IllegalArgumentException();
} else if ((B & 0x20) == 0) {
utf8Tail = 1;
ucs4Char = B & 0x1F;
minUcs4Char = 0x80;
} else if ((B & 0x10) == 0) {
utf8Tail = 2;
ucs4Char = B & 0x0F;
minUcs4Char = 0x800;
} else if ((B & 0x08) == 0) {
utf8Tail = 3;
ucs4Char = B & 0x07;
minUcs4Char = 0x10000;
} else if ((B & 0x04) == 0) {
utf8Tail = 4;
ucs4Char = B & 0x03;
minUcs4Char = 0x200000;
} else if ((B & 0x02) == 0) {
utf8Tail = 5;
ucs4Char = B & 0x01;
minUcs4Char = 0x4000000;
} else {
// First UTF-8 can not be 0xFF or 0xFE
throw new IllegalArgumentException();
}
if (k + 3 * utf8Tail > length)
throw new IllegalArgumentException();
for (int j = 0; j != utf8Tail; j++) {
if (str.charAt(k) != '%')
throw new IllegalArgumentException();
B = unHex(str.charAt(k + 1), str.charAt(k + 2));
if (B < 0 || (B & 0xC0) != 0x80)
throw new IllegalArgumentException();
ucs4Char = (ucs4Char << 6) | (B & 0x3F);
k += 3;
}
// Check for overlongs and other should-not-present codes
if (ucs4Char < minUcs4Char || ucs4Char == 0xFFFE
|| ucs4Char == 0xFFFF) {
ucs4Char = 0xFFFD;
}
if (ucs4Char >= 0x10000) {
ucs4Char -= 0x10000;
if (ucs4Char > 0xFFFFF)
throw new IllegalArgumentException();
char H = (char) ((ucs4Char >>> 10) + 0xD800);
C = (char) ((ucs4Char & 0x3FF) + 0xDC00);
buf[bufTop++] = H;
} else {
C = (char) ucs4Char;
}
}
if (fullUri && URI_DECODE_RESERVED.indexOf(C) >= 0) {
for (int x = start; x != k; x++) {
buf[bufTop++] = str.charAt(x);
}
} else {
buf[bufTop++] = C;
}
}
}
return (buf == null) ? str : new String(buf, 0, bufTop);
}
private static boolean encodeUnescaped(char c, boolean fullUri) {
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
|| ('0' <= c && c <= '9')) {
return true;
}
if ("-_.!~*'()".indexOf(c) >= 0) //$NON-NLS-1$
return true;
if (fullUri) {
return URI_DECODE_RESERVED.indexOf(c) >= 0;
}
return false;
}
private static final String URI_DECODE_RESERVED = ";/?:@&=+$,#"; //$NON-NLS-1$
/*
* Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
* least 6 bytes long. Return the number of UTF-8 bytes of data written.
*/
private static int oneUcs4ToUtf8Char(byte[] utf8Buffer, int ucs4Char) {
int utf8Length = 1;
//JS_ASSERT(ucs4Char <= 0x7FFFFFFF);
if ((ucs4Char & ~0x7F) == 0)
utf8Buffer[0] = (byte) ucs4Char;
else {
int i;
int a = ucs4Char >>> 11;
utf8Length = 2;
while (a != 0) {
a >>>= 5;
utf8Length++;
}
i = utf8Length;
while (--i > 0) {
utf8Buffer[i] = (byte) ((ucs4Char & 0x3F) | 0x80);
ucs4Char >>>= 6;
}
utf8Buffer[0] = (byte) (0x100 - (1 << (8 - utf8Length)) + ucs4Char);
}
return utf8Length;
}
private static char toHexChar(int i) {
if (i >> 4 != 0)
throw new IllegalArgumentException();
return (char) ((i < 10) ? i + '0' : i - 10 + 'A');
}
private static int unHex(char c) {
if ('A' <= c && c <= 'F') {
return c - 'A' + 10;
} else if ('a' <= c && c <= 'f') {
return c - 'a' + 10;
} else if ('0' <= c && c <= '9') {
return c - '0';
} else {
return -1;
}
}
private static int unHex(char c1, char c2) {
int i1 = unHex(c1);
int i2 = unHex(c2);
if (i1 >= 0 && i2 >= 0) {
return (i1 << 4) | i2;
}
return -1;
}
private static boolean isWindows() {
if (IS_WINDOWS == null) {
// Use reflection to obtain platform information to make it
// easy for testing.
//
// Original code:
//
// IS_WINDOWS = Platform.OS_WIN32.equals(Platform.getOS());
//
try {
Class<?> platformClass = Class
.forName("org.eclipse.core.runtime.Platform"); //$NON-NLS-1$
String os = (String) platformClass.getDeclaredMethod("getOS") //$NON-NLS-1$
.invoke(null);
String os_win32 = (String) platformClass
.getDeclaredField("OS_WIN32").get(null); //$NON-NLS-1$
if (os_win32 != null && os != null) {
IS_WINDOWS = Boolean.valueOf(os_win32.equals(os));
}
} catch (Throwable e) {
}
if (IS_WINDOWS == null)
IS_WINDOWS = Boolean.FALSE;
}
return IS_WINDOWS.booleanValue();
}
private static class PathDisambiguator {
URI uri;
String head;
String neck;
File body;
public PathDisambiguator(URI uri, File file) {
this.uri = uri;
this.head = file.getName();
this.neck = null;
this.body = file.getParentFile();
}
public void increaseDepth() {
if (body == null)
return;
String name = body.getName();
body = body.getParentFile();
if (neck == null) {
neck = name;
} else {
neck = name + File.separator + neck;
}
}
public boolean conflictsWith(PathDisambiguator that) {
return that != null && this.head.equals(that.head)
&& (this.neck == null || this.neck == that.neck
|| (this.neck != null
&& this.neck.equals(that.neck)))
&& (this.body != null || that.body != null);
}
@Override
public String toString() {
return neck == null ? head
: String.format("%2$s [%3$s]", File.separator, head, neck); //$NON-NLS-1$
}
}
public static void calculateFileURILabels(URI[] inputURIs,
Map<URI, String> labels) {
List<PathDisambiguator> paths = new ArrayList<PathDisambiguator>();
for (int i = 0; i < inputURIs.length; i++) {
URI uri = inputURIs[i];
if (uri == null)
continue;
String uriValue = uri.toString();
if (FilePathParser.isFileURI(uriValue)) {
String path = FilePathParser.toPath(uriValue);
if (path != null) {
paths.add(new PathDisambiguator(uri, new File(path)));
}
}
}
for (int i = 1; i < paths.size(); i++) {
PathDisambiguator path1 = paths.get(i);
for (int j = 0; j < i; j++) {
PathDisambiguator path2 = paths.get(j);
while (path1.conflictsWith(path2)) {
path1.increaseDepth();
path2.increaseDepth();
}
}
}
for (PathDisambiguator path : paths) {
labels.put(path.uri, path.toString());
}
}
@SuppressWarnings("nls")
public static void main(String[] args) {
String absolutePath = "C:/bb/11/11/11/11/11";
String base = "C:/bb";
String relativePath = toRelativePath(base, absolutePath);
System.out.println(relativePath);
System.out.println(isPathRelative(relativePath));
// String absolutePath2 = toAbsolutePath(base, relativePath);
// System.out.println(absolutePath2);
// System.out.println("\\\\pa".startsWith(WIN_NETWORK_PATH_PREFIX));
// String relativePath2 = toRelativePath("c:/xm/cca", "\\\\xmksd/xmak");
// System.out.println(relativePath2 + "=======");
// System.out.println(new File("\\\\").getParentFile());
}
}