1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP; 4 import static org.robolectric.RuntimeEnvironment.getApiLevel; 5 6 import android.system.ErrnoException; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.nio.ByteBuffer; 10 import java.nio.ByteOrder; 11 import libcore.io.BufferIterator; 12 import libcore.io.MemoryMappedFile; 13 import libcore.io.Streams; 14 import org.robolectric.annotation.Implementation; 15 import org.robolectric.annotation.Implements; 16 import org.robolectric.shadow.api.Shadow; 17 18 /** 19 * This is used by Android to load and inferFromValue time zone information. Robolectric emulates 20 * this functionality by proxying to a time zone database file packaged into the android-all 21 * jar. 22 */ 23 @Implements(value = MemoryMappedFile.class, isInAndroidSdk = false) 24 public class ShadowMemoryMappedFile { 25 private byte[] bytes; 26 private static final String TZ_DATA_1 = "/misc/zoneinfo/tzdata"; 27 private static final String TZ_DATA_2 = "/usr/share/zoneinfo/tzdata"; 28 private static final String TZ_DATA_3 = "/misc/zoneinfo/current/tzdata"; 29 30 @Implementation 31 public static MemoryMappedFile mmapRO(String path) throws Throwable { 32 if (path.endsWith(TZ_DATA_1) || path.endsWith(TZ_DATA_2) || path.endsWith(TZ_DATA_3)) { 33 InputStream is = MemoryMappedFile.class.getResourceAsStream(TZ_DATA_2); 34 if (is == null) { 35 throw (Throwable) exceptionClass().getConstructor(String.class, int.class) 36 .newInstance("open", -1); 37 } 38 try { 39 MemoryMappedFile memoryMappedFile = new MemoryMappedFile(0L, 0L); 40 ShadowMemoryMappedFile shadowMemoryMappedFile = Shadow.extract(memoryMappedFile); 41 shadowMemoryMappedFile.bytes = Streams.readFully(is); 42 return memoryMappedFile; 43 } catch (IOException e) { 44 throw (Throwable) exceptionClass().getConstructor(String.class, int.class, Throwable.class) 45 .newInstance("mmap", -1, e); 46 } 47 } else { 48 throw new IllegalArgumentException("Unknown file for mmap: '" + path); 49 } 50 } 51 52 private static Class exceptionClass() { 53 if (getApiLevel() >= LOLLIPOP) { 54 return ErrnoException.class; 55 } else { 56 try { 57 return MemoryMappedFile.class.getClassLoader().loadClass("libcore.io.ErrnoException"); 58 } catch (ClassNotFoundException e) { 59 throw new RuntimeException(e); 60 } 61 } 62 } 63 64 @Implementation 65 public synchronized void close() throws Exception { 66 bytes = null; 67 } 68 69 @Implementation 70 public BufferIterator bigEndianIterator() { 71 return getHeapBufferIterator(ByteOrder.BIG_ENDIAN); 72 } 73 74 @Implementation 75 public BufferIterator littleEndianIterator() { 76 return getHeapBufferIterator(ByteOrder.LITTLE_ENDIAN); 77 } 78 79 private BufferIterator getHeapBufferIterator(ByteOrder endianness) { 80 return new RoboBufferIterator(bytes, endianness); 81 } 82 83 @Implementation 84 public int size() { 85 return bytes.length; 86 } 87 88 private static class RoboBufferIterator extends BufferIterator { 89 private final ByteBuffer buffer; 90 91 public RoboBufferIterator(byte[] buffer, ByteOrder order) { 92 this.buffer = ByteBuffer.wrap(buffer); 93 } 94 95 @Override public void seek(int offset) { 96 buffer.position(offset); 97 } 98 99 @Override public void skip(int byteCount) { 100 buffer.position(buffer.position() + byteCount); 101 } 102 103 @Override 104 public int pos() { 105 return 0; 106 } 107 108 @Override public void readByteArray(byte[] dst, int dstOffset, int byteCount) { 109 System.arraycopy(buffer.array(), buffer.position(), dst, dstOffset, byteCount); 110 skip(byteCount); 111 } 112 113 @Override public byte readByte() { 114 return buffer.get(); 115 } 116 117 @Override public int readInt() { 118 return buffer.getInt(); 119 } 120 121 @Override public void readIntArray(int[] dst, int dstOffset, int intCount) { 122 for (int i = 0; i < intCount; i++) { 123 dst[dstOffset + i] = buffer.getInt(); 124 } 125 } 126 127 @Override public short readShort() { 128 return buffer.getShort(); 129 } 130 } 131 } 132