by ampatspell
in Code
In my spare time I’m designing my first “real” Cocoa appplication and I’ve decided to be rather open about this app and it’s implementation details prior and after the release.
I’m not going to disclose anything about the app just yet. Well, maybe only that it will come with open-source cloud component deployable (or automatically deployed) to AppEngine.
While I’m still siting in Photoshop trying to come up with interface design, I wanted to create actual clickable buttons from mockup. Here is more or less step-by-step guide of drawing them.
But now about NSButton custom drawing.

In Cocoa NSButton is subclass of NSControl which are drawn using NSCells. So we need a new NSButtonCell subclass to do our drawing:
#import "AMDarkButtonCell.h" @implementation AMDarkButtonCell - (void)drawImage:(NSImage*)image withFrame:(NSRect)frame inView:(NSView*)controlView { } - (NSRect)drawTitle:(NSAttributedString*)title withFrame:(NSRect)frame inView:(NSView*)controlView { } - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView { } @end
My not so great custom button consists of only of image and bezel so I’m not going to override -drawTitle:withFrame:inView:.
Lets start with bezel. It’s composed from:
Cocoa provides:
NSBezierPath for drawing, stroking and clipping-to rounded rectanglesNSGradient for drawing multiple point gradientsThat’s basically all we need to draw whole background composite.
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView { NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; CGFloat roundedRadius = 3.0f; // Outer stroke (drawn as gradient) [ctx saveGraphicsState]; NSBezierPath *outerClip = [NSBezierPath bezierPathWithRoundedRect:frame xRadius:roundedRadius yRadius:roundedRadius]; [outerClip setClip]; NSGradient *outerGradient = [[NSGradient alloc] initWithColorsAndLocations: [NSColor colorWithDeviceWhite:0.20f alpha:1.0f], 0.0f, [NSColor colorWithDeviceWhite:0.21f alpha:1.0f], 1.0f, nil]; [outerGradient drawInRect:[outerClip bounds] angle:90.0f]; [outerGradient release]; [ctx restoreGraphicsState]; // Background gradient [ctx saveGraphicsState]; NSBezierPath *backgroundPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.0f, 2.0f) xRadius:roundedRadius yRadius:roundedRadius]; [backgroundPath setClip]; NSGradient *backgroundGradient = [[NSGradient alloc] initWithColorsAndLocations: [NSColor colorWithDeviceWhite:0.17f alpha:1.0f], 0.0f, [NSColor colorWithDeviceWhite:0.20f alpha:1.0f], 0.12f, [NSColor colorWithDeviceWhite:0.27f alpha:1.0f], 0.5f, [NSColor colorWithDeviceWhite:0.30f alpha:1.0f], 0.5f, [NSColor colorWithDeviceWhite:0.42f alpha:1.0f], 0.98f, [NSColor colorWithDeviceWhite:0.50f alpha:1.0f], 1.0f, nil]; [backgroundGradient drawInRect:[backgroundPath bounds] angle:270.0f]; [backgroundGradient release]; [ctx restoreGraphicsState]; // Dark stroke [ctx saveGraphicsState]; [[NSColor colorWithDeviceWhite:0.12f alpha:1.0f] setStroke]; [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1.5f, 1.5f) xRadius:roundedRadius yRadius:roundedRadius] stroke]; [ctx restoreGraphicsState]; // Inner light stroke [ctx saveGraphicsState]; [[NSColor colorWithDeviceWhite:1.0f alpha:0.05f] setStroke]; [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.5f, 2.5f) xRadius:roundedRadius yRadius:roundedRadius] stroke]; [ctx restoreGraphicsState]; // Draw darker overlay if button is pressed if([self isHighlighted]) { [ctx saveGraphicsState]; [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.0f, 2.0f) xRadius:roundedRadius yRadius:roundedRadius] setClip]; [[NSColor colorWithCalibratedWhite:0.0f alpha:0.35] setFill]; NSRectFillUsingOperation(frame, NSCompositeSourceOver); [ctx restoreGraphicsState]; } }
To draw image I’ve chosen to use it only as a mask for 2 draw operations:
- (void)drawImage:(NSImage*)image withFrame:(NSRect)frame inView:(NSView*)controlView { NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; CGContextRef contextRef = [ctx graphicsPort]; NSData *data = [image TIFFRepresentation]; // open for suggestions CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL); if(source) { CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); CFRelease(source); // Draw shadow 1px below image CGContextSaveGState(contextRef); { NSRect rect = NSOffsetRect(frame, 0.0f, 1.0f); CGFloat white = [self isHighlighted] ? 0.2f : 0.35f; CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef); [[NSColor colorWithDeviceWhite:white alpha:1.0f] setFill]; NSRectFill(rect); } CGContextRestoreGState(contextRef); // Draw image CGContextSaveGState(contextRef); { NSRect rect = frame; CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef); [[NSColor colorWithDeviceWhite:0.1f alpha:1.0f] setFill]; NSRectFill(rect); } CGContextRestoreGState(contextRef); CFRelease(imageRef); } }
Ok, now we have drawing code in place, lets move to Interface Builder and wire up this custom NSButtonCell for buttons which should be drawn with this style.
Add some buttons:

Set Image attribute in “Button Attributes” panel:

Click one more time on button in window to select it’s Cell Identity:

And set cell Class to our custom implementation:

That’s it.
by ampatspell
in Code
Few weeks ago I finished my first Mac application — Saucejas.app. It is small (about 1000 lines of code) app which renders pretty image and video slideshow to secondary display using CoreAnimation.
Application itself doesn’t have notably pretty UI or implementation. It was written basically in one day for use in Saucejas show as a informative & mood-setting background. Here is video featuring Saucejas and previous version of app in background which I wrote earlier in Processing.
Nevertheless it was a really good exercise for me and here are few things I’ve learned:
plist editor is not the easiest to use tool for NSArray of NSDictionaries of 2-3 key-value pairs — consider different format for large lists or create custom editor for plistNSSize SCProportionalSizeForTargetSize(NSSize size, NSSize target) { if(NSEqualSizes(size, target)) { return size; } float widthFactor = target.width / size.width; float heightFactor = target.height / size.height; float scalingFactor; if (widthFactor < heightFactor) { scalingFactor = widthFactor; } else { scalingFactor = heightFactor; } return NSMakeSize(size.width * scalingFactor, size.height * scalingFactor); } @implementation NSImage (SCAdditions) - (NSImage *)imageByScalingProportionallyToSize:(NSSize)target { NSSize size = self.size; NSSize proportionalSize = SCProportionalSizeForTargetSize(size, target); NSImage *scaledImage = [[NSImage alloc] initWithSize:proportionalSize]; [scaledImage setCacheMode:NSImageCacheNever]; [scaledImage lockFocus]; [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; [self drawInRect:NSMakeRect(.0f, .0f, proportionalSize.width, proportionalSize.height) fromRect:NSMakeRect(.0f, .0f, size.width, size.height) operation:NSCompositeSourceOver fraction:1.0f]; [scaledImage unlockFocus]; return [scaledImage autorelease]; } @end
NSImage can also be done in background threadNSImageCacheNever otherwise all scaled NSImages are cached// Periodically called by NSTimer - (void)disableScreenSaverTimerDidUpdate:(NSTimer *)timer { UpdateSystemActivity(OverallAct); }
#import <IOKit/graphics/IOGraphicsLib.h> @implementation SCDisplay + (NSArray *)displays { NSMutableArray *displays = [NSMutableArray array]; CGDirectDisplayID *onlineDisplays = malloc( sizeof(CGDirectDisplayID) * kSCMaxDisplayCount); CGDisplayCount displayCount; CGDisplayErr listErr = CGGetActiveDisplayList(kSCMaxDisplayCount, onlineDisplays, &displayCount); if(listErr == kCGErrorSuccess) { for(int i=0; i<displayCount; i++) { CGDirectDisplayID displayId = onlineDisplays[i]; CGRect bounds = CGDisplayBounds(displayId); NSString *name = nil; io_service_t port = CGDisplayIOServicePort(displayId); CFDictionaryRef infoRef = IODisplayCreateInfoDictionary(port, kIODisplayOnlyPreferredName); if(infoRef != NULL) { CFDictionaryRef productNameDictionaryRef = CFDictionaryGetValue(infoRef, CFSTR(kDisplayProductName)); if(productNameDictionaryRef != NULL) { CFIndex count = CFDictionaryGetCount(productNameDictionaryRef); if(count > 0) { const void **values = (const void **)malloc (sizeof(void *) * count); CFDictionaryGetKeysAndValues(productNameDictionaryRef, NULL, values); CFTypeRef nameRef = (CFTypeRef)values[0]; name = [NSString stringWithString:(NSString *)nameRef]; free(values); } } CFRelease(infoRef); } if(!name) { name = @"Unknown"; } SCDisplay *display = [[SCDisplay alloc] initWithDirectDisplayId:displayId bounds:bounds name:name]; [displays addObject:display]; [display release]; } } else { NSLog(@"%@ %s • CGGetOnlineDisplayList failed: %u", [self className], _cmd, listErr); } free(onlineDisplays); return displays; } @end
// CGDirectDisplayID displayId_; from CGGetActiveDisplayList // Caputure CGDisplayErr captureErr = CGDisplayCapture(self.displayId); if(captureErr == kCGErrorSuccess) { self.captured = YES; } else { NSLog(@"%@ %s • CGDisplayCapture failed: %u", [self className], _cmd, captureErr); } // Release CGDisplayErr err = CGDisplayRelease(self.displayId); if(err == kCGErrorSuccess) { self.captured = NO; released = YES; } else { NSLog(@"%@ %s • CGDisplayRelease failed: %u", [self className], _cmd, err); }
NSWindow on captured displayCGFloat mainDisplayHeight = [[NSScreen mainScreen] frame].size.height; CGRect displayBounds = self.display.bounds; // from CGDisplayBounds // secondary displays are "below" main screen NSRect rect = NSMakeRect(displayBounds.origin.x, mainDisplayHeight - displayBounds.size.height, displayBounds.size.width, displayBounds.size.height); NSWindow *window = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; [window setLevel:CGShieldingWindowLevel()];
-[NSScreen frame] & -[NSScreen visibleFrame] maybe they calculate correct rect for windowNSScreen from displayId I assume -[NSScreen deviceDescription] can be usedReferences:
by ampatspell
in Code
Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is fifth post about new features that will form 1.2 final release. The previous posts are: Inheritance, GaedsKey
Gaeds Model attribute to Entity property mapping is done using @Property annotation. By default attribute (class field) names are used also as Entity property names but this can be customized using @Property(name = "differentName"). So lets consider a small example:
@Model public class User { @PrimaryKey private GaedsKey<User> key; @Property(name = "fullName") private String login; @Property private String password; } User user = new User(); user.setLogin("ampatspell"); user.setPassword("zeebaneighba"); sess.put(user);
Entities will have the following properties:
User user = sess.get(user); Entity entity = Gaeds.asModel(user).getEntity(); // accessing Entity directly assertEquals("ampatspell", entity.getProperty("fullName")); assertEquals("zeebaneighba", entity.getProperty("password"));
And now the breaking change: query filters and sort parameters now use attribute names instead of entity property names. This means that to filter by login attribute, the "login" must be used, not "fullName":
User user; // SELECT * from User WHERE fullName = ampatspell user = sess.query().model(User.class).eq("login", "ampatspell").singleResult(); assertNotNull(user); // SELECT * from User WHERE password = zeebaneighba user = sess.query().model(User.class).eq("password", "zeebaneighba").singleResult(); assertNotNull(user);
Please check your DAO’s for queries which filter by custom property names and update them to attribute names.
I’m sorry that I found this bug only now. The behavior could be left as is but I feel this breaking change is needed.
by ampatspell
in Code
Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is forth post about new features that will form 1.2 final release.
The first three are:
Both new features are related to inheritance handling so lets start with 3 models:
@Model public class Transaction { } @Model(discriminator = "handled") public class HandledTransaction extends Transaction { } @Model(discriminator = "amount") public class AmountTransaction extends HandledTransaction { }
Those three models shares single kind of "Transaction".
Now it is possible to query for keysOnly() while getting (more) correct model classes:
Iterable<HandledTransaction> iterable = getSession().query(). model(HandledTransaction.class). eq("failed", false). eq("otherTransactionKey", null). limit(limit). keysOnly(). asIterable(); for (HandledTransaction transaction : iterable) { // ... }
Prepared query is:
SELECT __key__
FROM Transaction
WHERE _discriminator = handled
AND failed = false
AND otherTransactionKey = NULL
While keysOnly() query doesn’t return actual type (In this case it can also be AmountTransaction — see next feature why) at least it is not base Transaction instance anymore.
Here is all three queries:
session.query().model(<Model>.class).keysOnly().asList();
| Model and returned instance class | DatastoreService Query |
|---|---|
Transaction.class |
SELECT __key__ FROM Transaction |
HandledTransaction.class |
SELECT __key__ FROM Transaction WHERE _discriminator = handled |
AmountTransaction.class |
SELECT __key__ FROM Transaction WHERE _discriminator = amount |
Of course this also works for SELECT * FROM queries with the difference that returned instance clases will be actual sub-classes:
session.query().model(<Model>.class).asList();
| Model and returned instance class | DatastoreService Query |
|---|---|
Transaction.class |
SELECT * FROM Transaction |
HandledTransaction.class |
SELECT * FROM Transaction WHERE _discriminator = handled |
AmountTransaction.class |
SELECT * FROM Transaction WHERE _discriminator = amount |
As we saw previously to query any HandledTransaction (this includes all HandledTransaction and also AmountTransaction entities) we need to query with two different "_discriminator" values. This is not supported in AppEngine Datastore. Instead if given model is subclass of model which is already a model what has discriminator value set, the "_discriminator" property becomes list property listing all discriminators:
| Model | Discriminators |
|---|---|
Transaction.class |
— |
HandledTransaction.class |
“handled” — String |
AmountTransaction.class |
[“amount”, “handled”] — List<String> |
This allows to query any entity that is HandledTransaction or AmountTransaction.
To take advantage of those new features, enties what has parent entity with discriminator set (in this example only AmountTransaction), needs to be migrated.
To migrate just get or query them followed by put afterwards:
Session session = getSession(); // Query will work because this was the _discriminator value that was saved before // SELECT * FROM Transaction WHERE _discriminator = amount Iterable<AmountTransaction> list = session.query(). model(AmountTransaction.class). limit(500). asIterable(); // gaeds will re-populate _discriminator value session.put(list);
by ampatspell
in Code
Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is third post in three post series about new features that will form 1.2 final release.
The first two are:
Google AppEngine Datastore Entity primary keys are Key instances. Each Key is composed from String kind and either long id or String keyName. Also Key can be encoded as String and then decoded back to Key instance using KeyFactory.
For me the most important new gaeds feature is GaedsKey<T> generic class that wraps Key and respective model class. It is impossible to create GaedsKey with mismatched model class (see notes below about inheritance).
GaedsKey
public interface GaedsKey<T> extends Comparable<GaedsKey<T>> { Key getKey(); Class<? extends T> getModelClass(); <R> GaedsKey<R> cast(Class<R> type); <P> GaedsKey<P> getParent(Class<? extends P> model); String encode(); // and few other methods }
To create GaedsKey from Key or from encoded String GaedsKeyFactory must be used:
GaedsKeyFactory kf = session.key(); // or @Inject GaedsKeyFactory directly // from String or Key GaedsKey<Task> key = kf.create(string, Task.class); // from encoded datastore key GaedsKey<Task> key = kf.create(datastoreKey, Task.class); // from datastore key // Root keys GaedsKey<Task> key = kf.create(Task.class, "something"); // with keyName GaedsKey<Task> key = kf.create(Task.class, 102l); // with id // Keys with parent Key or GaedsKey GaedsKey<Task> key = kf.create(parent, Task.class, "something"); // with keyName GaedsKey<Task> key = kf.create(parent, Task.class, 102l); // with id
First two DatastoreKeyFactory#create calls ensures that provided key matches model class by comparing Key kind to kind declared for model. If kind don’t match, GaedsKeyRuntimeException is thrown. This behavior ensures that encoded keys coming from web request matches expected model kind. If kinds doesn’t match, exception is thrown before Datastore get/put/query operation (fail fast). By using Key directly get operations would quite possibly just fail with ClassCastException after touching Datastore.
String string = keyToString(createKey("User", 101)); // Creates "User(101)" key Key key = stringToKey(string); // Throws ModelNotFoundException or ClassCastException after get. Task task = sess.get(key); // Throws GaedsKeyRuntimeException GaedsKey<Task> key = kf.create(string, Task.class); // Not reached Task task = sess.get(key);
Gaeds models has direct relationship to Entity keyNames. It is possible to use GaedsKey<T> without any changes to Model classes but more beneficial they are if @PrimaryKey, @ParentKey and all @Property Key declarations also are declared as GaedsKey<T>. Lets consider 2 models – Person and Task:
@Model public static class Person { @PrimaryKey private GaedsKey<Person> key; // getters & setters } @Model public static class Task { @PrimaryKey private GaedsKey<Task> key; @Property private String title; @Property private GaedsKey<Person> owner; // getters & setters }
And some straight-forward operations on them:
// First request // create person Person person = new Person(); sess.put(person); // create task with person set as owner Task task = new Task(); task.setTitle("Hello world"); task.setOwner(person.getKey()); sess.put(task); String encoded = task.getKey().encode(); // Second request // Create Task key from encoded string (will throw if encoded Key is not for Task kind) GaedsKey<Task> taskKey = sess.key().create(encoded, Task.class); // get Task, get Person Task task = sess.get(taskKey); Person person = sess.get(task.getOwner());
Task{
key=GaedsKey{Task(2) com.ampatspell.gaeds.test.Task},
title='Hello world',
owner=GaedsKey{Person(1) com.ampatspell.gaeds.test.Person}
}
Person{
key=GaedsKey{Person(1) com.ampatspell.gaeds.test.Person}
}
As we can see, the GaedsKey Session API is the same as for Key and that we needed to access GaedsKeyFactory only once — for string decoding. While GaedsKey API is identical to Key based there are a bit more going on behind the scenes.
Not only GaedsKey creation is validated to conform model-kind relationship, also all model attributes that has GaedsKey type are validated before put and after get, query.
Lets consider a bit different example involving gaeds model inheritance:
@Model public static class Person { @PrimaryKey private GaedsKey<? extends Person> key; } @Model(discriminator = "administrator") public static class Administrator extends Person { }
We have a new class Administrator which extends Person. Gaeds implements inheritance by transparently adding "_discriminator" property to saved entities and because there is no way knowing which entity type is before getting actual Entity, GaedsKey can be created with incorrect key+type pair:
Administrator admin = new Administrator(); sess.put(admin); Person person = new Person(); sess.put(person); String key = person.getKey().encode(); // creating a key with Person Datastore Key and Administrator type GaedsKey<Administrator> admin = kf.create(key, Administrator.class); // Throws an GaedsKeyRuntimeException sess.get(admin);
After Entity is retrieved from Datastore, its type is compared to required type for GaedsKey, if GaedsKey type is not the same or subclass of fetched Model’s class, GaedsKeyRuntimeException is thrown. This prevents getting ClassCastException or incorrect Model when sess.get returns. This also works for messed up GaedsKey unchecked casts:
@Model public static class Task { // ... @Property private GaedsKey<Administrator> owner; }
Person person = new Person(); sess.put(person); // Unchecked cast GaedsKey<Administrator> ownerKey = (GaedsKey) person.getKey(); Task task = new Task(); task.setOwner(ownerKey); // throws GaedsKeyRuntimeException sess.put(task);
com.ampatspell.gaeds.internal.key.GaedsKeyRuntimeException:
GaedsKey{ExamplePerson(2) com.ampatspell.gaeds.test.Person} model class is not assignable
to com.ampatspell.gaeds.test.Administrator
Also GaedsKey exposes cast(T targetType) method that allows up- and down-casting GaedsKey instances but it enforces that:
GaedsKey<Person> cannot be up-casted to GaedsKey<Administrator>GaedsKey<Administrator> can be down-casted to GaedsKey<Person> and then up-casted back to GaedsKey<Administrator>But note: it is possible to create GaedsKey<Administrator> with Person Key and Administrator type, save it in Datastore as GaedsKey<Administrator>. Try fetching entity using that GaedsKey before, only if fetch succeeds, we can be confident that given GaedsKey<Administrator> actually points to Administrator.
For gets and queries, model @Property attributes there is a bit different story. Lets start with creating Task with Zeeba as an owner instead of Administrator:
Entity task = new Entity("Task"); task.setProperty("owner", createKey("Zeeba", "fake")); Key dsKey = ds.put(task); GaedsKey<Task> key = kf.create(dsKey, Task.class); Task model = sess.get(key); assertTrue(Gaeds.asModel(model).getIncompatibleProperties().contains("owner"));
WARNING: Entity to model mapping cast errors
Model: com.ampatspell.gaeds.test.Task
Entity: Task(1)
* 'owner'
com.google.appengine.api.datastore.Key =>
com.ampatspell.gaeds.GaedsKey com.ampatspell.gaeds.test.Administrator (Zeeba("fake"))
As we can see, this is also considered incompatibility because Session wouldn’t allow persisting GaedsKey<Zeeba> instead of GaedsKey<Administrator>. Useful for Entity migration.
Session and Session#query() methods now takes either Key or GaedsKeyGaedsKeyFactory can be injected (@Inject) directly or accessed from Session#key()GaedsBpSupport and GaedsDaoSupport inherits from AbstractGaedsSupport which also implements #key()GaedsKey @Property, @PrimaryKey and @ParentKey can be declared as:
GaedsKey<Task>GaedsKey<? extends Task>GaedsKey<T> for generic classes (@Model class Task<T> { ... })I’ve migrated 10 or so models to GaedsKey for one of the projects I’m currently working on. All tests pass, everything looks fine but I’ll wait few more days before releasing 1.2 final.
by ampatspell
in Code
Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is second post in three post series about new features that will form 1.2 final release.
Since first release of gaeds it has Model property value conversion support from-to Entity properties using Converter. There are few default converters and its easy to write additional ones. To declare that property needs converter, @Property annotation has type setting:
@Model public class Something { @Property(type = Text.class) private String text; }
For Something.text property before creating or updating Entity Converter<String, Text> will kick in and convert String value to Text and after get or query operations, the same converter will convert Text back to String and set it to model property.
But now lets consider the following property:
@Model public class Something { @Property(field = Serializable.class, type = Blob.class) private Map<String, Integer> paymentDetails; }
The Converter lookup is done using newly added @Property.field, if it is not provided, using actual field type. Because in this example @Property.field is set, the Converter<Serializable, Blob> is used instead of Converter<Map, Blob>.
Also see Gaeds 1.2-SNAPSHOT Changes #2 — Converters to learn about changes in Converter configuration.
by ampatspell
in Code
Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. I’ve started the work on version 1.2 what will have few nice additions but for now a small breaking change.
Previous versions was configured something like this:
public class GaedsModule extends AbstractModule { @Override protected void configure() { install(new GaedsBaseModule()); bind(DatastoreService.class).annotatedWith(Gaeds.class). toProvider(DatastoreServiceProvider.class); bind(IncompatiblePropertiesLogger.class). to(IncompatiblePropertiesLoggerImpl.class); } @Provides @Singleton public List<Converter> provideConverters( @Named("default") List<Converter> defaultConverters) { return defaultConverters; } @Provides @ModelClasses @Singleton public List<Class<?>> provideModelClasses() { List<Class<?>> classes = new ArrayList<Class<?>>(); classes.add(Zeeba.class); classes.add(Larry.class); return classes; } }
Now configuration looks a lot better:
public class GaedsModule extends AbstractGaedsModelsModule { @Override protected void configureModels() { install(new GaedsBaseModule()); bind(DatastoreService.class).annotatedWith(Gaeds.class). toProvider(DatastoreServiceProvider.class); bind(IncompatiblePropertiesLogger.class). to(IncompatiblePropertiesLoggerImpl.class); // Models model(Zeeba.class); model(Larry.class); // Converters useDefaultConverters(true); converter(SomeCustomConverter.class); } }
In addition to to cleaner gaeds module, model classes and converters are bound using Multibinder so it’s perfectly fine to install multiple AbstractGaedsModule derived modules in one Guice Injector. There are only two things to keep in mind:
Model class and Converter duplicates are not allowed (guice will complain)useDefaultConverters(boolean) only once (in one AbstractGaedsModule)by ampatspell
in Code
This post is about Models — the GWT library which hopefully will greatly help to build client-server communication, model properties-to-view binding and in application architecture in general by simplifying all boilderplate code needed to manage downloaded object graphs, presenting them to users and allowing users to act on them. What I have currently is initial implementation of KVC, KVO, Bindings, ForwardingModel, ListController, ObjectController and DataSource (with DataStore). Nothing are release ready. Controllers and DataSource are so badly implemented I’m a bit ashamed to show them.
The main purpose of post (aside from loud-thinking) is to try to get some feedback before API is locked. So if you have some comments, critique or just want to say hi, feel free to mail me to ampatspell@gmail.com.
Lets start from lowest level API — Key-Value Coding and Key-Value Observing (the terms come from Foundation framework in Cocoa). Basically this is easy to use property change observation and property value setting by name support for classes. In whole Model library context the classes are model objects either fetched from DataSource (server) or created locally.
The basic model property change observation looks like this:
User user = GWT.create(User.class); ObserverRegistration registration = asReflection(user).observe("address.country", String.class, new Observer<String>() { public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, String oldValue, String newValue) { // called after value change } }); registration.remove(); ObservableReflection reflection = ((Observable) entry).getReflection();
Observer<T> callback is called when either of following are called:
user.setAddress(newAddress);user.getAddress().setCountry("New country");Callback is not called with initial value. See bindings why this is not needed.
Operation can be one of SET, INSERT, REMOVE, CLEAR and so on (SET for properties, others for List operations).
IndexSet is Range like class for defining List operation indexes (list.addAll(int index, E element) notifies observers about INSERT operation and IndexSet with single index).
asReflection(Object) is statically imported helper method what just casts user instance to Observable.
The second low-level building block is Key-Value Coding that allows to set Observable object properties not only by using setters directly but also by key path. “address.country” in previous code snippet is key path.
Lets look at small example using the same user entity:
asReflection(user).setValueForKeyPath("address.country", "Zeebaburg");
this is the same as calling user.getAddress().setCountry("Zeebaburg") (with null checking) but can be used to dynamically set values for arbitrary keys (properties).
Of course for both of those things to work GWT generator is used. This is the reason why GWT.create() is required and so this code will only work in GWT runtime and unittests run with GWTTestCase. Currently I don’t have Java reflection and cglib based implementations for Observable but it can be created to support standalone JUnit unittests. For now I’m ignoring this while I’m not finished with API design.
Next low-level API is Bindings. This feature allows to “bind” two targets (BindingTarget) so if one of them is changed, the other is notified with new optionally converted value. Also when binding is created initial value is set from “left” target to “right”.
Currently I have three notable binding targets:
ObservableTarget — for Observable modelsHasValueTarget<T> — for view bindingsHasTextTarget — for view bindingsOthers are easy to write.
As you might guessed this main purpose is to bind model properties to GWT Widgets. But actually it turns out that binding ObservableTarget to other ObservableTarget is as important as Widget targeted ones.
Lets look at two low-level Bindings usage examples:
bindings.bind( observable(list, "selection", BlogEntry.class), observable(selected, "content", BlogEntry.class) );
bindings.bind( observable(model, "key", String.class), hasText(view.key()) ); // transformer is required because binding target types differ // otherwide the code won't compile bindings.bind( observable(model, "status", Integer.class), hasText(view.status()), new StatusToStringArrayTransformer() );
"bindings" variable is instance of Bindings which allows to create and bind new bindings (and store them in DeferredCollection which allows concurrent modifications by deferring adds, removes, clear after iterator has finished) and unbind them with one bindings.clear() call.
The binding itself is created like this:
Binding<L, R> binding = new Binding<L, R>(left, right, transformer); binding.bind(); // ... binding.unbind();
Bindings and Binding by themselselves doesn’t require GWT.create at least now but ObservableTarget needs Observable of course.
The last low-level tool is Forwarding which is Observable proxy object that represents other Observable object while trying to be invisible for its clients. This is sometimes needed for current DataSource API implementation and I think this won’t change.
To create Model (Observable) and it’s Forwarding instance we start with 2 classes. GWT.create doesn’t take parameters so Forwarding must be additional class (or better yet interface. But for now it is class.)
public class BlogEntry implements IsModel { String title; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } public class BlogEntryForwarding extends BlogEntry implements IsModel, IsForwarding { }
Now we can GWT.create them and use:
BlogEntry model = GWT.create(BlogEntry.class); BlogEntry proxy = GWT.create(BlogEntryForwarding.class); // asForwarding is just "safe" cast to Forwarding w/ assert Forwarding proxyForwarding = Forwardings.asForwarding(proxy); proxyForwarding.setForwardingTarget(model); // the same for model observers (except setForwardingTarget change) Observables.asReflection(proxy).observe("title", String.class, new Observer<String>(){ void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, T oldValue, T newValue) { // 1st property change assertEquals(null, oldValue); assertEquals("Title #1", newValue); // 2nd property change assertEquals("Title #1", oldValue); assertEquals("Title #2", newValue); // result of setForwardingTarget. Old model is _not_ notified about this change obviosly assertEquals("Title #2", oldValue); assertEquals("New Title", newValue); } }); // model property change model.setTitle("Title #1"); assertEquals("Title #1", proxy.getTitle()); // proxy property change proxy.setTitle("Title #2"); assertEquals("Title #2", model.getTitle()); // proxy target change BlogEntry newModel = GWT.create(BlogEntry.class); newModel.setTitle("New Title"); proxyForwarding.setForwardingTarget(newModel);
And that’s it. We can move on to Model Controllers.
Before we talk about Model controllers I need to draw a distinction between Observable and Model. First Model is Observable, second it has status represented as int bitfield (yes, not EnumSet or whatever. This I’m almost certain should be called premature optimization). Status is observable property.
Ok, finally model controllers.
Now API has 2 (really badly implemented) ModelControllers:
ListControllerObjectControllerBoth should be created using GWT.create to enable observation support for them.
ListController has “content” and “selection” (only single selection currently). Both properties are observable, both publicly available. When ListController content changes, selection is set to null.
ObjectController has observable “content” property.
// bind ListController.selection to ObjectController.content bindings.bind(observable(list, "selection", BlogEntry.class), observable(selected, "content", BlogEntry.class)); // observe ObjectController.content changes asReflection(selected).observe("content", BlogEntry.class, new Observer<BlogEntry>() { public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, BlogEntry oldValue, BlogEntry newValue) { if (newValue != null) ds.fetchDetails(newValue); } });
Also both controllers allows Presenters (or whatever else) to add itself as Target. List controller target has callbacks to:
clear – called when list is cleared (maybe speeds up DOM modifications, haven’t checked)create – called when Widget for new list controller entry should be created. It is passed to bind afterwardsbind – called when Widget needs to be rebound to new entry (only bound, no rebinding yet)remove, insert and other things not exposed yetHere is one ListController to UI Target implementation (declared in Presenter):
blogEntries.addTarget(new ListControllerTarget<BlogEntry, BlogEntryListView>() { public void clear() { view.clearListEntries(); } public BlogEntryListView create() { return view.addListEntry(); } public void bind(Bindings bindings, BlogEntry model, BlogEntryListView entry) { bindings.bind( observable(model, "title", String.class), hasText(entry.subject()) ); bindings.bind( observable(model, "published", Boolean.class), hasText(entry.marker()), new BooleanToPublishedStatusTransformer() ); // when rebind will be supported, HandlerRegistration will be managed by ListController entry.selectionClickHandlers().addClickHandler(new ClickHandler() { public void onClick(ClickEvent clickEvent) { if (view != null) { int index = view.getListEntryIndex(clickEvent); blogEntries.setSelection(index); } } }); } });
hasText is statically imported method which asks HasText and returns HasTextTarget.
While this ListControllerTarget interface is too specific for controller-view binding it suffices for now.
From example we can see how easy is to bind to list entries and how terrible current bindings api look. For Model-* related bindings I’m planning to implement easier to use and reuse API. See below.
ObjectController Target interface is similar:
registration = selection.addTarget(new ObjectControllerTarget<BlogEntry>() { public void bind(Bindings bindings, final BlogEntry model) { // general info bindings.bind( observable(model, "key", String.class), hasText(view.key()) ); bindings.bind( observable(model, "status", Integer.class), hasText(view.status()), new StatusToStringArrayTransformer() ); bindings.bind( observable(model, "title", String.class), hasText(view.title()) ); // bindings.bind( observable(model, "hasDetails", Boolean.class), hasBoolean(view.showDetails()) ); // details bindings.bind( observable(model, "body", String.class), hasText(view.body()) ); bindings.bind( observable(model, "created", Date.class), hasText(view.created()), new DateToStringTransformer() ); bindings.bind( observable(model, "published", Boolean.class), hasText(view.published()), new BooleanToPublishedStatusTransformer() ); } });
Called when “content” is changed. null value will have separate callback also create callback will be here to support switching UI from null placeholder to actual view.
For general Target API — specific user Target API I’m thinking of something along those lines:
registration = selection.addTarget(simpleBindableTarget(new SimpleBindableTarget<BlogEntry(){ public void onBind(Bindings bindings, BlogEntry model) { /// ... } }));
where simpleBindableTarget is statically imported method that takes simplified, more targeted to usage interface and implements general ObjectControllerTarget.
The other approach would be just using ObjectController observation directly (still by using some simpleBindableTarget as a helper) and to bind UI against ObjectController itself (which would forward events to and from its content). Not yet decided.
So far we have observable models and controllers that helps to map model graph and selection to UI components. Now I’ll try to describe DataSource and Model state.
As stated previously, Model has state integer. It has the following values:
int READY = 1 << 1; int DIRTY = 1 << 2; int BUSY = 1 << 3; int ERROR = 1 << 4; int LOADING = 1 << 5; int SAVING = 1 << 6; int DESTROYING = 1 << 7; int REFRESHING = 1 << 8; int NEW = 1 << 9; int DESTROYED = 1 << 10;
When model is locally created (transient), it has “NEW | DIRTY” state, when it is currently loading from DataSource (async), the state is set to “BUSY | LOADING”. The state is observable, so view can show “Please wait” while loading, refreshing or deleting model objects.
DataSource has a bit different approach managing asynchronous operations. Basically all DataSource methods are seemingly synchronous. Lets look how the list fetching happens:
public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> { public List<BlogEntry> fetchAll() { return fetchList( new GetBlogEntriesAction(), new DispatchDataSourceCallback<GetBlogEntriesResult, DataSourceList<BlogEntry>>() { public void prepare(DataSourceList<BlogEntry> model, GetBlogEntriesResult result) { populateList(model, result.getEntries()); } }); } }
As we can see from previous code snippet, fetchAll() returns immediately. It returns empty ListModel<BlogEntry> instance which can be set as a content to some ListController. Also the list implements IsModel so it has state property. It is set to “BUSY | LOADING”. When asynchronous call returns, DispatchDataSourceCallback#prepare is called with Result (Command pattern) and the same list what method returned previously. Now its time to add entries to the list. When it happens, List → ListController → Presenter → UI chain is invoked so new entries automatically appears in view.
In similar fashion also single models are fetched:
public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> { public BlogEntry fetchLatestEntry() { return fetchSingle( new GetLatestBlogEntry(), new DispatchDataSourceCallback<GetBlogEntryResult, BlogEntry>() { public void prepare(BlogEntry model, GetLatestBlogEntryResult result) { populateModel(model, result.getBlogEntry()); } }); } }
Returned BlogEntry has all properties set to null and state “BUSY | LOADING”. All would be fine but this way if fetchLatestBlogEntry() is called multiple times, multiple BlogEntry instances for one actual blog entry row/entity would be created. And not always unique model primary key is known prior fetching it (as in this example).
So here comes small trick – instead of returning actual BlogEntry, the BlogEntryForwarding proxy is returned with null or dummy BlogEntry as target. When async returns, DispatchDataSourceCallback#populateModel(BlogEntry, GetLatestBlogEntryResult) is called and it updates BlogEntry key what can be checked for uniqueness in local DataStore. If model was already fetched previously, its properties are updated with new ones, BlogEntryForwarding proxy target is changed to that previous model instance but latest fetched is simply discarded. This process also works for each list item fetched.
Key comparison is done using IsModel#getKey() method what must be implemented by each model.
If model key is known prior fetching (fetchBlogEntryByKey(String key)), DataSource returns model instance without creating a proxy.
The benefits are:
DataSource can transparently cache modelsThe things that needs to be implemented are:
fetchLatestEntry” – DataStore should know that “latestEntry” is fetched already before fetching it and not only after fetch find out that model with given key already exists in DataStore. This also applies to lists (“fetchAllEntries” with GAE DataStore cursor or Hibernate/JDBC page number)DataSource should be aware of difference)While basic binding api can look so technical, bindings from observable/model to view should have special approach. Firstly bindings are both target type agnostic, for model-view bindings left side is always model.
Previously I had model-view bindings what looks like this:
binder.model(User.class).with(model(new ModelBuilderDelegate<User>() { public void bind(ModelBinder<User> b, User model) { b.property("key", String.class). to(hasValue(view.getKey())). to(hasText(view.getKeyValue())); b.property("name", String.class). to(hasValue(view.getName())). to(hasText(view.getNameValue())); b.property("login", String.class). to(hasValue(view.getLogin())). to(hasText(view.getLoginValue())); b.model("address", Address.class).with(model(new ModelBuilderDelegate<Address>() { public void bind(ModelBinder<Address> b, Address model) { b.property("street", String.class). to(hasValue(view.getStreet())). to(hasText(view.getStreetValue())); b.property("country", String.class). to(hasValue(view.getCountry())). to(hasText(view.getCountryValue())); b.model("details", Details.class).with(model(new ModelBuilderDelegate<Details>() { public void bind(ModelBinder<Details> b, Details model) { b.property("description", String.class). to(hasValue(view.getAddressDescription())). to(hasText(view.getAddressDescriptionValue())); } })); } })); } }));
Now I haven’t touched that code yet but here is that test form running (also with list handling): http://models.latest.ampatspell-test.appspot.com/
While gwt-pectin and nearly all other binding frameworks also handle validation I feel that validation support should/must be separated from model-view bindings simply because it would be great to run the same validation definition in server-side. I deem it very useful. Also if validators are not instantiated directly, few validators what can not be run in GWT, can be skipped there and only run at server-side.
bones-validator does that (but API is far from great).
public class UserValidationModule extends AbstractValidationModule<UserDto> { @Override public void prepare(UserDto o) { field("name", o.getName()).with(Unique.class); // runs only in server side field("name", o.getName()).with(NotBlank.class); // allowBlank() is EmailValidator instance method call for this particular field field("email", o.getEmail()).with(Email.class).allowBlank(); } }
If model-view bindings are aware of validation (if needs to be), I think it can be done while keeping bindings and validation APIs clean.
by ampatspell
in Code
Just deployed latest SNAPSHOT of maven archetypes:
bones-google-quickstart-archetype (Google GWT & AppEngine Web App With Bones and Gaeds)google-quickstart-archetype (Google GWT & AppEngine Web App)They’re finally updated to latest dependencies and even compile and run.
mvn archetype:generate -DarchetypeCatalog=http://www.amateurinmotion.com/repository
To try them out make sure you have Maven 2.2.1 and run:
$ mvn archetype:generate -DarchetypeCatalog=http://www.amateurinmotion.com/repository
...
Choose archetype:
1: http://www.amateurinmotion.com/repository -> google-quickstart-archetype ↩
(Google GWT & AppEngine Web App)
2: http://www.amateurinmotion.com/repository -> bones-google-quickstart-archetype ↩
(Google GWT & AppEngine Web App With Bones and Gaeds)
Choose a number: (1/2): 2
...
Define value for groupId: : com.zeeba
Define value for artifactId: : zeeba
Define value for version: 1.0-SNAPSHOT: :
Define value for package: com.zeeba: :
Confirm properties configuration:
groupId: com.zeeba
artifactId: zeeba
version: 1.0-SNAPSHOT
package: com.zeeba
Y: :
...
$ cd zeeba
$ mvn install # compiles GWT module, packs war ready for AppEngine deployment
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Bones Quickstart ...................................... SUCCESS [1.631s]
[INFO] Quickstart -- Shared .................................. SUCCESS [1.480s]
[INFO] Quickstart -- GWT ..................................... SUCCESS [30.084s]
[INFO] Quickstart -- Server .................................. SUCCESS [2.208s]
[INFO] Quickstart -- War ..................................... SUCCESS [4.255s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 40 seconds
...
$ cd zeeba-gwt
$ mvn google:gwt-run # to run GWT in Development Mode
$ cd zeeba-war
$ mvn google:appengine-run # to run AppEngine local server with compiled GWT module
To deploy app to AppEngine, set your login, password in ~/.m2/settings.xml:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <profiles> <profile> <id>appengine</id> <properties> <appengine-email> ... </appengine-email> <appengine-password> ... </appengine-password> </properties> </profile> </profiles> <activeProfiles> <activeProfile>appengine</activeProfile> </activeProfiles> </settings>
Run:
$ mvn deploy # needs maven distributionManagement/repository or $ mvn install $ cd zeeba-war $ mvn google:appengine-deploy
Info about google-maven-plugin can be found at plugin information site.
Plugin and Bones also can be used with Springframework outside AppEngine environment.
by ampatspell
in Code
Just got working the first prototype implementation of ListController for GWT (name comes from Cocoa NSArrayController).
Here is small screencast that shows List<BlogEntry> observation and “reaction” to element adds (ListController) and element property changes (Bindings). Video has no sound, better viewed full-screen.
ListController<T> takes content observable List<T> and observes ADD (later will also handle REMOVE, REPLACE, CLEAR) operations. When it happens, calls:
ListControllerTarget#create that should add the widget to the screenListControllerTarget#bind that should bind widget to model propertiesblogEntries.addDelegate(new ListControllerTarget<BlogEntry, BlogEntryListView>() { public BlogEntryListView create() { return view.addListEntry(); } public void bind(Bindings bindings, BlogEntry model, BlogEntryListView view) { // bind BlogEntry#title to HasText subject() bindings.bind(observable(model, "title", String.class), hasText(view.subject())); // bind BlogEntry#published to HasText marker() using boolean to marker text transformer // (which transforms true to "Published" and false to "Draft") bindings.bind(observable(model, "published", Boolean.class), hasText(view.marker()), new BooleanToMarkerTransformer()); } });
ObservableList<T> is an ArrayList<T> subclass which implements IsObservable and overrides add, remove mutators and notifies about element changes.
BlogEntry implements IsModel which extends IsObservable and also is observable.
Observation and Bindings are based on IsObservable » Observable:
BlogEntry entry = GWT.create(BlogEntry.class) // needs GWT generator ObserverRegistration reg = ((Observable) entry).getReflection().observe("title", String.class, new Observer<String>() { public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, String oldValue, String newValue) { // "title" value has changed } }); reg.remove(); // to remove observer when it is not needed anymore
Next: ListController selection, ObjectController, DataSource a la NSManagedObjectContext / SproutCode DataSource or something like that.