1 /* 2 * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de> 3 * 4 * This library is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU Lesser General Public License as published 6 * by the Free Software Foundation; either version 2.1 of the License, or 7 * (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 12 * License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this library; If not, write to the Free Software Foundation, Inc., 16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 */ 18 19 package de.waldheinz.fs.fat; 20 21 import java.math.BigInteger; 22 import java.util.Arrays; 23 24 /** 25 * Represents a "short" (8.3) file name as used by DOS. 26 * 27 * @author Matthias Treydte <waldheinz at gmail.com> 28 */ 29 final class ShortName { 30 31 /** 32 * These are taken from the FAT specification. 33 */ 34 private final static byte[] ILLEGAL_CHARS = { 35 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 36 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C 37 }; 38 39 /** 40 * The name of the "current directory" (".") entry of a FAT directory. 41 */ 42 public final static ShortName DOT = new ShortName(".", ""); // NOI18N 43 44 /** 45 * The name of the "parent directory" ("..") entry of a FAT directory. 46 */ 47 public final static ShortName DOT_DOT = new ShortName("..", ""); // NOI18N 48 49 private final char[] name; 50 private boolean mShortNameOnly; 51 ShortName(String nameExt)52 private ShortName(String nameExt) { 53 if (nameExt.length() > 12) 54 throw new IllegalArgumentException("name too long"); 55 56 final int i = nameExt.indexOf('.'); 57 final String nameString, extString; 58 59 if (i < 0) { 60 nameString = nameExt.toUpperCase(); 61 extString = ""; 62 } else { 63 nameString = nameExt.substring(0, i).toUpperCase(); 64 extString = nameExt.substring(i + 1).toUpperCase(); 65 } 66 67 this.name = toCharArray(nameString, extString); 68 checkValidChars(this.name); 69 } 70 ShortName(String name, String ext)71 ShortName(String name, String ext) { 72 this.name = toCharArray(name, ext); 73 } 74 ShortName(char[] name)75 ShortName(char[] name) { 76 this.name = name; 77 } 78 ShortName(char[] nameArr, char[] extArr)79 public ShortName(char[] nameArr, char[] extArr) { 80 char[] result = new char[11]; 81 System.arraycopy(nameArr, 0, result, 0, nameArr.length); 82 System.arraycopy(extArr, 0, result, 8, extArr.length); 83 this.name = result; 84 } 85 toCharArray(String name, String ext)86 private static char[] toCharArray(String name, String ext) { 87 checkValidName(name); 88 checkValidExt(ext); 89 90 final char[] result = new char[11]; 91 Arrays.fill(result, ' '); 92 System.arraycopy(name.toCharArray(), 0, result, 0, name.length()); 93 System.arraycopy(ext.toCharArray(), 0, result, 8, ext.length()); 94 95 return result; 96 } 97 98 /** 99 * Calculates the checksum that is used to test a long file name for it's 100 * validity. 101 * 102 * @return the {@code ShortName}'s checksum 103 */ checkSum()104 public byte checkSum() { 105 final byte[] dest = new byte[11]; 106 for (int i = 0; i < 11; i++) 107 dest[i] = (byte) name[i]; 108 109 int sum = dest[0]; 110 for (int i = 1; i < 11; i++) { 111 sum = dest[i] + (((sum & 1) << 7) + ((sum & 0xfe) >> 1)); 112 } 113 114 return (byte) (sum & 0xff); 115 } 116 117 /** 118 * Parses the specified string into a {@code ShortName}. 119 * 120 * @param name the name+extension of the {@code ShortName} to get 121 * @return the {@code ShortName} representing the specified name 122 * @throws IllegalArgumentException if the specified name can not be parsed 123 * into a {@code ShortName} 124 * @see #canConvert(java.lang.String) 125 */ get(String name)126 public static ShortName get(String name) throws IllegalArgumentException { 127 if (name.equals(".")) 128 return DOT; 129 else if (name.equals("..")) 130 return DOT_DOT; 131 else 132 return new ShortName(name); 133 } 134 135 /** 136 * Tests if the specified string can be converted to a {@code ShortName}. 137 * 138 * @param nameExt the string to test 139 * @return if the string can be converted 140 * @see #get(java.lang.String) 141 */ canConvert(String nameExt)142 public static boolean canConvert(String nameExt) { 143 /* TODO: do this without exceptions */ 144 try { 145 ShortName.get(nameExt); 146 return true; 147 } catch (IllegalArgumentException ex) { 148 return false; 149 } 150 } 151 parse(byte[] data)152 public static ShortName parse(byte[] data) { 153 final char[] nameArr = new char[8]; 154 155 for (int i = 0; i < nameArr.length; i++) { 156 nameArr[i] = (char) LittleEndian.getUInt8(data, i); 157 } 158 159 final char[] extArr = new char[3]; 160 for (int i = 0; i < extArr.length; i++) { 161 extArr[i] = (char) LittleEndian.getUInt8(data, 0x08 + i); 162 } 163 164 return new ShortName(nameArr, extArr); 165 } 166 write(byte[] dest)167 public void write(byte[] dest) { 168 for (int i = 0; i < 11; i++) { 169 dest[i] = (byte) name[i]; 170 } 171 } 172 asSimpleString()173 public String asSimpleString() { 174 return new String(this.name).trim(); 175 } 176 177 @Override toString()178 public String toString() { 179 StringBuilder sb = new StringBuilder(); 180 for (int i = 0; i < this.name.length; i++) { 181 sb.append(Integer.toHexString(name[i])); 182 sb.append(' '); 183 } 184 return getClass().getSimpleName() + 185 " [" + 186 asSimpleString() + " -- " + 187 sb.toString() + "]"; // NOI18N 188 } 189 checkValidName(String name)190 private static void checkValidName(String name) { 191 checkString(name, "name", 1, 8); 192 } 193 checkValidExt(String ext)194 private static void checkValidExt(String ext) { 195 checkString(ext, "extension", 0, 3); 196 } 197 checkString(String str, String strType, int minLength, int maxLength)198 private static void checkString(String str, String strType, 199 int minLength, int maxLength) { 200 201 if (str == null) 202 throw new IllegalArgumentException(strType + 203 " is null"); 204 if (str.length() < minLength) 205 throw new IllegalArgumentException(strType + 206 " must have at least " + minLength + 207 " characters: " + str); 208 if (str.length() > maxLength) 209 throw new IllegalArgumentException(strType + 210 " has more than " + maxLength + 211 " characters: " + str); 212 } 213 214 @Override equals(Object obj)215 public boolean equals(Object obj) { 216 if (!(obj instanceof ShortName)) { 217 return false; 218 } 219 220 final ShortName other = (ShortName) obj; 221 return Arrays.equals(name, other.name); 222 } 223 224 @Override hashCode()225 public int hashCode() { 226 return Arrays.hashCode(this.name); 227 } 228 setHasShortNameOnly(boolean hasShortNameOnly)229 public void setHasShortNameOnly(boolean hasShortNameOnly) { 230 mShortNameOnly = hasShortNameOnly; 231 } 232 hasShortNameOnly()233 public boolean hasShortNameOnly() { 234 return mShortNameOnly; 235 } 236 237 /** 238 * Checks if the specified char array consists only of "valid" byte values 239 * according to the FAT specification. 240 * 241 * @param chars the char array to test 242 * @throws IllegalArgumentException if invalid chars are contained 243 */ checkValidChars(char[] chars)244 public static void checkValidChars(char[] chars) 245 throws IllegalArgumentException { 246 247 if (chars[0] == 0x20) 248 throw new IllegalArgumentException( 249 "0x20 can not be the first character"); 250 251 for (int i = 0; i < chars.length; i++) { 252 if ((chars[i] & 0xff) != chars[i]) 253 throw new IllegalArgumentException("multi-byte character at " + i); 254 255 final byte toTest = (byte) (chars[i] & 0xff); 256 257 if (toTest < 0x20 && toTest != 0x05) 258 throw new IllegalArgumentException("character < 0x20 at" + i); 259 260 for (int j = 0; j < ILLEGAL_CHARS.length; j++) { 261 if (toTest == ILLEGAL_CHARS[j]) 262 throw new IllegalArgumentException("illegal character " + 263 ILLEGAL_CHARS[j] + " at " + i); 264 } 265 } 266 } 267 } 268