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 &lt;waldheinz at gmail.com&gt;
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