NHMLFixup v10
[jpcrr.git] / org / jpc / diskimages / TreeDirectoryFile.java
blob4ea16522c9efffa163c1b9459ed3ad13454f7eec
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org.jpc.diskimages;
32 import java.io.*;
33 import java.util.*;
35 public class TreeDirectoryFile extends TreeFile
37 //We use TreeMap instead of HashMap here because TreeMap is ordered and HashMap is not.
38 protected TreeMap<String,TreeFile> entries;
39 //Volume name. Very special.
40 protected String volumeName;
41 //Timestamp for entries.
42 private int dosTime;
43 private String _timestamp;
44 //Cached key position and key.
45 protected int cachedPosition;
46 protected String cachedKey;
48 private static byte convertCodepoint(int cpoint) throws IOException
50 if(cpoint == 33 || (cpoint >= 35 && cpoint <= 41) || cpoint == 45 || cpoint == 125 || cpoint == 126)
51 return (byte)cpoint; //Misc.
52 if(cpoint >= 48 && cpoint <= 57) //0-9
53 return (byte)cpoint;
54 if(cpoint >= 64 && cpoint <= 90) //@A-Z
55 return (byte)cpoint;
56 if(cpoint >= 97 && cpoint <= 122) //a-z
57 return (byte)(cpoint - 32);
58 if(cpoint >= 94 && cpoint <= 123) //^_`a-z{
59 return (byte)cpoint;
60 if(cpoint >= 160 && cpoint <= 255) //High range.
61 return (byte)cpoint;
62 throw new IOException("Character " + (char)cpoint + " not allowed in filename.");
65 public static void writeEntryName(byte[] sector, String name, int offset, boolean noExtension) throws IOException
67 for(int i = 0; i < 11; i++)
68 sector[offset + i] = 32; //Pad with spaces.
69 int split = name.indexOf(".");
70 if(!noExtension) {
71 if(split >= 0 && (split < 1 || split > 8 || name.length() > split + 4) || (split < 0 && name.length() > 8))
72 throw new IOException("Illegal file name " + name + ".");
73 } else if(name.length() < 1 || name.length() > 11)
74 throw new IOException("Illegal file name " + name + ".");
76 if(split < 0)
77 split = name.length(); //Dirty hack.
79 String mainName = name.substring(0, split);
80 String extName;
81 if(split > 0 && split < name.length())
82 extName = name.substring(split + 1);
83 else
84 extName = "";
85 for(int i = 0; i < mainName.length(); i++) {
86 sector[offset + i] = convertCodepoint(mainName.charAt(i));
88 for(int i = 0; i < extName.length(); i++) {
89 sector[offset + 8 + i] = convertCodepoint(extName.charAt(i));
93 public TreeDirectoryFile(String self, String timestamp) throws IOException
95 super(self);
96 entries = new TreeMap<String,TreeFile>();
97 cachedPosition = 0;
98 cachedKey = null;
99 volumeName = null;
100 if(timestamp == null)
101 timestamp = "19900101000000";
102 _timestamp = timestamp;
103 dosTime = dosFormatTimeStamp(timestamp);
106 public TreeDirectoryFile(String self, String volume, String timestamp) throws IOException
108 this(self, timestamp);
109 volumeName = volume;
112 public void setClusterZeroOffset(int offset)
114 doSetClusterZeroOffset(offset);
115 Map.Entry<String,TreeFile> entry = entries.firstEntry();
116 while(entry != null) {
117 entry.getValue().setClusterZeroOffset(offset);
118 entry = entries.higherEntry(entry.getKey());
122 public void setClusterSize(int size)
124 doSetClusterSize(size);
125 Map.Entry<String,TreeFile> entry = entries.firstEntry();
126 while(entry != null) {
127 entry.getValue().setClusterSize(size);
128 entry = entries.higherEntry(entry.getKey());
132 public int getSize()
134 int extra = 0;
135 if(volumeName != null)
136 extra = 1;
137 return 32 * entries.size() + extra;
140 public static int dosFormatTimeStamp(String stamp) throws IOException
142 if(stamp.length() != 14)
143 throw new IOException("Invalid timestamp " + stamp + ".");
144 try {
145 long nstamp = Long.parseLong(stamp, 10);
146 if(nstamp < 19800101000000L || nstamp > 21071231235959L)
147 throw new IOException("Invalid timestamp " + stamp + ".");
148 int year = (int)(nstamp / 10000000000L) - 1980;
149 int month = (int)(nstamp % 10000000000L / 100000000L);
150 int day = (int)(nstamp % 100000000L / 1000000L);
151 int hour = (int)(nstamp % 1000000L / 10000L);
152 int minute = (int)(nstamp % 10000L / 100L);
153 int second = (int)(nstamp % 100L) / 2;
154 if(month < 1 || month > 12 || day < 0 || day > 31 || hour > 23 || minute > 59 || second > 59)
155 throw new IOException("Invalid timestamp " + stamp + ".");
156 if(day == 30 && (month == 2 || month == 4 || month == 6 || month == 9 || month == 11))
157 throw new IOException("Invalid timestamp " + stamp + ".");
158 if(day == 29 && month == 2 && (year % 4 != 0 || year == 120))
159 throw new IOException("Invalid timestamp " + stamp + ".");
160 return year * 33554432 + month * 2097152 + day * 65536 + hour * 2048 + minute * 32 + second;
161 } catch(NumberFormatException e) {
162 throw new IOException("Invalid timestamp " + stamp + ".");
166 public void readSector(int sector, byte[] data) throws IOException
168 int extraEntries = 0;
169 if(volumeName != null)
170 extraEntries++;
171 if(sector >= (entries.size() + extraEntries + 15) / 16) { //16 entries per sector.
172 for(int i = 0; i < 512; i++)
173 data[i] = 0;
174 return;
177 if(cachedKey == null || cachedPosition > 16 * sector) {
178 //Cache is unusable.
179 cachedPosition = extraEntries;
180 try {
181 cachedKey = entries.firstKey();
182 } catch(Exception e) {
183 cachedKey = null;
186 while(cachedPosition < 16 * sector) {
187 cachedPosition++;
188 cachedKey = entries.higherKey(cachedKey);
191 for(int i = 0; i < 16; i++) {
192 //Write zeroes as entry (at least for intialization). Also write the time.
193 for(int j = 0; j < 32; j++)
194 data[32 * i + j] = 0;
195 data[32 * i + 22] = (byte)(dosTime & 0xFF);
196 data[32 * i + 23] = (byte)((dosTime >> 8) & 0xFF);
197 data[32 * i + 24] = (byte)((dosTime >> 16) & 0xFF);
198 data[32 * i + 25] = (byte)((dosTime >> 24) & 0xFF);
200 if(volumeName != null && sector == 0 && i == 0) {
201 //The special volume file.
202 writeEntryName(data, volumeName, 32 * i, true);
203 data[32 * i + 11] = 8; //Volume file -A -R -H -S.
204 //Cluster 0 and size 0.
205 } else if(cachedKey != null) {
206 TreeFile file = entries.get(cachedKey);
207 //Name of entry.
208 writeEntryName(data, cachedKey, 32 * i, false);
209 //Varous other stuff.
210 if(file instanceof TreeDirectoryFile) {
211 data[32 * i + 11] = 16; //Directory file -A -R -H -S.
212 } else {
213 data[32 * i + 11] = 0; //Regular file -A -R -H -S.
215 int size = file.getSize();
216 int cluster = file.getStartCluster();
217 if(size == 0) cluster = 0; //Handle empty files.
218 data[32 * i + 26] = (byte)(cluster & 0xFF);
219 data[32 * i + 27] = (byte)((cluster >>> 8) & 0xFF);
220 data[32 * i + 28] = (byte)(size & 0xFF);
221 data[32 * i + 29] = (byte)((size >>> 8) & 0xFF);
222 data[32 * i + 30] = (byte)((size >>> 16) & 0xFF);
223 data[32 * i + 31] = (byte)((size >>> 24) & 0xFF);
225 cachedPosition++;
226 cachedKey = entries.higherKey(cachedKey);
231 public void readSectorEnd()
233 //No resources to free.
236 public int assignCluster(int base)
238 setStartCluster(base);
239 base += getSizeInClusters();
240 if(base < 2)
241 base = 2; //Special case for root directory.
242 Map.Entry<String,TreeFile> entry = entries.firstEntry();
243 while(entry != null) {
244 base = entry.getValue().assignCluster(base);
245 entry = entries.higherEntry(entry.getKey());
247 return base;
250 public TreeFile nextFile()
252 Map.Entry<String,TreeFile> entry = entries.firstEntry();
253 if(entry == null) {
254 if(parent == null)
255 return null;
256 return parent.nextFile(selfName);
258 return entry.getValue();
261 protected TreeFile nextFile(String key)
263 Map.Entry<String, TreeFile> entry = entries.higherEntry(key);
264 if(entry == null) {
265 if(parent == null)
266 return null;
267 return parent.nextFile(selfName);
269 return (TreeFile)entry.getValue();
272 public void addFile(TreeFile newFile) throws IOException
274 entries.put(newFile.getSelfName(), newFile);
275 newFile.parentTo(this);
278 public TreeDirectoryFile pathToDirectory(String name, String walked, String timestamp) throws IOException
280 if(name == null || "".equals(name))
281 return this;
283 int split = name.indexOf('/');
284 String remaining;
285 String walk;
286 if(split == -1) {
287 walk = name;
288 remaining = "";
289 } else {
290 walk = name.substring(0, split);
291 remaining = name.substring(split + 1);
293 if(walked != null && walked != "")
294 walked = walked + "/" + walk;
295 else
296 walked = walk;
299 if(!entries.containsKey(walk)) {
300 addFile(new TreeDirectoryFile(walk, timestamp));
303 Object out = entries.get(walk);
304 if(out == null)
305 throw new IOException("What? Didn't I create " + walked + "???");
306 if(!(out instanceof TreeDirectoryFile))
307 throw new IOException("Conflicting types for " + walked + ". Was regular file, now should be directory?");
308 TreeDirectoryFile out2 = (TreeDirectoryFile)out;
309 return out2.pathToDirectory(remaining, walked, timestamp);
312 public static void importTree(File directory, String unixFileName, TreeDirectoryFile rootDir, String timestamp)
313 throws IOException
315 TreeDirectoryFile dir = rootDir.pathToDirectory(unixFileName, null, timestamp);
317 if(!directory.isDirectory())
318 throw new IOException("Okay, who passed non-directory " + directory.getAbsolutePath() + " to importTree()?");
319 File[] objects = directory.listFiles();
320 if(objects == null)
321 throw new IOException("Can't read directory " + directory.getAbsolutePath() + ".");
322 for(int i = 0; i < objects.length; i++) {
323 if(objects[i].isDirectory()) {
324 if(unixFileName == null)
325 importTree(objects[i], objects[i].getName(), rootDir, timestamp);
326 else
327 importTree(objects[i], unixFileName + "/" + objects[i].getName(), rootDir, timestamp);
328 } else {
329 //It's a regular file.
330 dir.addFile(new TreeRegularFile(objects[i].getName(), objects[i].getAbsolutePath()));
335 public static TreeDirectoryFile importTree(String fsPath, String volumeName, String timestamp) throws IOException
337 TreeDirectoryFile root = new TreeDirectoryFile("", volumeName, timestamp);
338 TreeDirectoryFile.importTree(new File(fsPath), null, root, timestamp);
339 return root;
342 private String nformatwidth(int number, int width)
344 String x = (new Integer(number)).toString();
345 while(x.length() < width)
346 x = " " + x;
347 return x;
350 public List<String> getComments(String prefix, String stamp)
352 List<String> l = new ArrayList<String>();
354 if(stamp == null)
355 stamp = "N/A ";
357 String dirMD5 = "N/A ";
359 l.add("Entry: " + stamp + " " + dirMD5 + " " + nformatwidth(entries.size(), 10) + " " + prefix + "/");
361 if(volumeName != null) {
362 l.add("Vname: " + _timestamp + " " + dirMD5 + " " + nformatwidth(0, 10) + " " + volumeName);
365 Map.Entry<String,TreeFile> entry = entries.firstEntry();
366 while(entry != null) {
367 List<String> sublist = entry.getValue().getComments(prefix + "/" + entry.getKey(), _timestamp);
368 l.addAll(sublist);
369 entry = entries.higherEntry(entry.getKey());
372 return l;