/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.apps.jpartition.model;
import java.util.Collections;
import java.util.List;
import org.jnode.fs.FileSystem;
import org.jnode.fs.Formatter;
/**
* A virtual device is used to represents the state of a physical device
* after all pending operations have been applied. It's used by the user
* interface to display the expected final result.
*
* @author Fabien DUMINY (fduminy@jnode.org)
*
*/
public class Device implements Bounded {
private final String name;
private final long size;
private final List<Partition> partitions;
private final org.jnode.driver.Device device;
private final boolean hasPartititionTable;
Device(String name, long size, org.jnode.driver.Device device, List<Partition> partitions) {
this.name = name;
this.size = size;
this.partitions = partitions;
this.device = device;
this.hasPartititionTable = !partitions.isEmpty();
}
/**
* Get the name if this virtual device.
* @return name if this virtual device.
*/
public final String getName() {
return name;
}
/**
* Get the size if this virtual device.
* @return size if this virtual device.
*/
public final long getSize() {
return size;
}
/**
* Say if the virtual device has a partition table.
* @return <code>true</code> if the virtual device has a partition table.
*/
public final boolean hasPartititionTable() {
return hasPartititionTable;
}
/**
* Get the list of partitions of this virtual device.
* @return
*/
public final List<Partition> getPartitions() {
checkPartitionned();
return Collections.unmodifiableList(partitions);
}
/**
* {@inheritDoc}
*/
public final long getEnd() {
return size - 1;
}
/**
* {@inheritDoc}
*/
public final long getStart() {
return 0;
}
/**
* @return true if this virtual device is equals to the given object.
*/
public final boolean equals(Object o) {
if (!(o instanceof Device)) {
return false;
}
Device other = (Device) o;
return name.equals(other.name);
}
/**
* Get the hashcode of this virtual device.
*/
public final int hashCode() {
return name.hashCode();
}
/**
* Get the physical device associated to this virtual device.
* @return
*/
final org.jnode.driver.Device getDevice() {
return device;
}
/**
* Add a partition to this virtual device.
* @param start The start of this partition (included).
* @param size The size of this partition.
* @return The new virtual partition.
*/
final Partition addPartition(long start, long size) {
final long end = (start + size - 1);
checkBounds(this, "start", start);
checkBounds(this, "end", end);
int index = findPartition(start, false);
if (index < 0) {
throw new DeviceException("can't add a partition in a used one");
}
Partition oldPart = partitions.get(index);
checkBounds(oldPart, "end", end);
Partition newPart = new Partition(start, size, true);
if (oldPart.getSize() == size) {
// replace the unused partition
partitions.set(index, newPart);
} else if (start == oldPart.getStart()) {
// the new partition
partitions.add(index, newPart);
// after the new partition
oldPart.setBounds(newPart.getEnd() + 1, oldPart.getSize() - size);
partitions.set(index + 1, oldPart);
} else if (end == oldPart.getEnd()) {
// before the new partition
oldPart.setSize(oldPart.getSize() - size);
// the new partition
partitions.add(index + 1, newPart);
} else {
long beginSize = start - oldPart.getStart();
long endSize = oldPart.getSize() - size - beginSize;
// before the new partition
oldPart.setSize(beginSize);
// the new partition
partitions.add(index + 1, newPart);
// after the new partition
partitions.add(index + 2, new Partition(end + 1, endSize, false));
}
return newPart;
}
/**
* Remove a partition from this virtual device.
* @param offset An offset that should match a used partition in this virtual device.
*/
final void removePartition(long offset) {
int index = findPartition(offset, true);
if (index < 0) {
throw new DeviceException("can't remove an empty partition");
}
Partition part = partitions.get(index);
long start = part.getStart();
long size = part.getSize();
if (index > 0) {
Partition partBefore = partitions.get(index - 1);
if (!partBefore.isUsed()) {
// merge with previous empty partition
start = partBefore.getStart();
size += partBefore.getSize();
partitions.remove(index);
index--;
}
}
if (index < (partitions.size() - 1)) {
Partition partAfter = partitions.get(index + 1);
if (!partAfter.isUsed()) {
// merge with following empty partition
size += partAfter.getSize();
partitions.remove(index + 1);
}
}
partitions.set(index, new Partition(start, size, false));
}
/**
* Format a partition of this virtual device.
* @param offset An offset that should match a used partition in this virtual device.
* @param formatter The formatter to use.
*/
final void formatPartition(long offset, Formatter<? extends FileSystem<?>> formatter) {
int index = findPartition(offset, true);
if (index < 0) {
throw new DeviceException("can't format an empty partition");
}
Partition part = partitions.get(index);
part.format(formatter);
}
/**
* Find a partition in this virtual device.
* @param offset An offset that should match a used partition in this virtual
* device.
* @param used true if the matching partition must be used.
* @return The index (zero based) of the partition in this virtual device, -1
* if there is no partition at the given offset or its <i>used</i> state
* doesn't match the <code>used</code> parameter.
*/
private final int findPartition(long offset, boolean used) {
checkPartitionned();
checkOffset(offset);
int result = -1;
int index = 0;
for (Partition currentPart : partitions) {
if (currentPart.contains(offset) && (currentPart.isUsed() == used)) {
result = index;
break;
}
index++;
}
return result;
}
/**
* Checks that the given offset is valid.
* @param offset The offset to check.
* @throws DeviceException if the offset is not valid.
*/
private final void checkOffset(long offset) {
if ((offset < 0) || (offset >= size)) {
throw new DeviceException("offset(" + offset + ") out of bounds. should be >=0 and <" +
size);
}
}
/**
* Checks that this virtual device is partitioned.
* @throws DeviceException if this virtual device is not partitioned.
*/
private final void checkPartitionned() {
if (!hasPartititionTable) {
throw new DeviceException("device has no partition table");
}
}
/**
* Checks that the given value is inside the bounds.
* @param bounded The bounds inside which the value should be.
* @param name The name of the value (used in case a {@link DeviceException} is thrown.
* @param value The value to check.
* @throws DeviceException if the value is not inside the bounds.
*/
private final void checkBounds(Bounded bounded, String valueName, long value) {
if (value < bounded.getStart()) {
throw new DeviceException(valueName + " must be >= " + bounded.getStart());
}
if (value > bounded.getEnd()) {
throw new DeviceException(valueName + " must be <= " + bounded.getEnd());
}
}
}