Tuesday, December 20, 2011

Just posted a plugin to use JRuby from SBT.  Pretty limited right now, but potentially useful.  Available on github as sbt-jruby.

Wednesday, October 26, 2011

Brief review of gadgets from Google IO 2011, five months later

At Google IO 2011, swag was received.  Two things really stand out:

Samsung Galaxy Tab 10.1 This rocks.  I use it constantly.  It’s a great dev device, too, since it’s really fast.  (That does require also using dev code on an el cheapo phone too – my choice for that are a couple Samsung Europas I bought in Ireland for E50.)  The only “problem” is that I have to compete with my wife for the device.  We’ve got a Fire on order that should solve that problem.  (I’d buy another Galaxy, but as a developer I feel like I need lots of new devices.  My wife laughs when she hears that.)

Chromebook.  I can’t even be bothered to find a link to these things.  I tried to use it, but it’s slow and has a nasty screen.  Feels like an obsolete laptop I discarded a couple years ago.  I don’t even know where it is, and I realized I can’t even tell you how long it’s been missing.

Sunday, September 25, 2011

Using the jruby eclipse plugin to generate an interface implemention

I started using asm to put together some ruby code generation scripts for the Eclipse plugin.  This one prints out a list of method definitions and their java signatures when you select an interface in the package explorer. If you select this interface
package com.restphone.jrubyeclipse;

import org.eclipse.jface.viewers.ISelection;

public interface IJrubyFilter {
  String do_filter(ISelection s);

  Integer someOtherMethod();
}
You'll get this output:
java_signature "java.lang.String do_filter (org.eclipse.jface.viewers.ISelection a)"
def do_filter *args
end

java_signature "java.lang.Integer someOtherMethod ()"
def someOtherMethod *args
end
Checked in to github as ruby_filter_interface.rb.

Friday, September 23, 2011

More JRuby + asm

Turns out you can get this jruby from yesterday's post even more concise:
require 'java'
require 'asm-3.3.1'
require 'pp'

# This lets me type org.objectweb.asm::... instead of Java::OrgObjectwebAsm::...
def org
  Java::Org
end

class SampleVisitor
  include org.objectweb.asm::MethodVisitor
  include org.objectweb.asm::ClassVisitor
  include org.objectweb.asm::FieldVisitor
  include org.objectweb.asm::AnnotationVisitor
  include org.objectweb.asm.signature::SignatureVisitor

  def method_missing name, *args
    name_string = name.to_s
    if name_string =~ /visit.*/
      puts "#{name}\t#{args}"
      self
    else
      super
    end
  end
end

# Loop through each .class file given on the command line
ARGV.each do |classfile|
  puts " ---------------- Reading file #{classfile}"
  f = java.io.File.new classfile
  fis = java.io.FileInputStream.new f
  
  class_reader = org.objectweb.asm::ClassReader.new fis
  
  v = SampleVisitor.new
  
  class_reader.accept v, 0
  
  puts
end
And the output is:
---------------- Reading file /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class
visit   [50, 33, "com/restphone/classSignature/SampleOne", nil, "java/lang/Object", #<#<Class:0x1144f3ba2>:0x2f6a23cf>]
visitSource     ["SampleOne.java", nil]
visitField      [9, "x", "Ljava/lang/Integer;", nil, nil]
visitEnd        []
visitMethod     [8, "<clinit>", "()V", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x2b071e12>]
visitLineNumber [4, #<Java::OrgObjectwebAsm::Label:0x2b071e12>]
visitFieldInsn  [178, "com/restphone/classSignature/SampleTwo", "y", "Ljava/lang/Integer;"]
visitFieldInsn  [179, "com/restphone/classSignature/SampleOne", "x", "Ljava/lang/Integer;"]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x575c13ef>]
visitLineNumber [3, #<Java::OrgObjectwebAsm::Label:0x575c13ef>]
visitInsn       [177]
visitMaxs       [1, 0]
visitEnd        []
visitMethod     [1, "<init>", "()V", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0xc303a60>]
visitLineNumber [3, #<Java::OrgObjectwebAsm::Label:0xc303a60>]
visitVarInsn    [25, 0]
visitMethodInsn [183, "java/lang/Object", "<init>", "()V"]
visitInsn       [177]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x66869470>]
visitLocalVariable      ["this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0xc303a60>, #<Java::OrgObjectwebAsm::Label:0x66869470>, 0]
visitMaxs       [1, 1]
visitEnd        []
visitMethod     [1, "doubleTheValue", "(Ljava/lang/Integer;)Ljava/lang/Integer;", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x10fa706d>]
visitLineNumber [7, #<Java::OrgObjectwebAsm::Label:0x10fa706d>]
visitVarInsn    [25, 1]
visitMethodInsn [182, "java/lang/Integer", "intValue", "()I"]
visitInsn       [5]
visitInsn       [104]
visitMethodInsn [184, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"]
visitInsn       [176]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x629a99eb>]
visitLocalVariable      ["this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x10fa706d>, #<Java::OrgObjectwebAsm::Label:0x629a99eb>, 0]
visitLocalVariable      ["x", "Ljava/lang/Integer;", nil, #<Java::OrgObjectwebAsm::Label:0x10fa706d>, #<Java::OrgObjectwebAsm::Label:0x629a99eb>, 1]
visitMaxs       [2, 2]
visitEnd        []
visitEnd        []
I'm including the jars on the command line:
jruby -I /Users/james/experimements/AsmSample/libs --1.9 asm_support.rb /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class  | less

Thursday, September 22, 2011

JRuby + Asm: easy processing of .class files

I started working with Asm this week.  After playing with it for a bit, it seemed obvious that JRuby would be a sweet spot for Asm code; very often, you need to implement only a small fraction of the interfaces that Asm uses.

Here’s my first JRuby effort with Asm.  It’s straightforward and doesn’t do much yet – just outputs the contents of the .class files given on the command line.  Here’s the JRuby:

require 'java'
require 'asm-3.3.1'
require 'pp'

# This lets me type org.objectweb.asm::... instead of Java::OrgObjectwebAsm::...
def org
  Java::Org
end

# This lets me use ClassReader without typing out org.objectweb.asm::ClassReader
java_import org.objectweb.asm::ClassReader

# Ruby supports mixins.  Both visitor classes (class and method) use this method_missing
# call.
#
# The generic visitor just prints information for any call to a visitor* method.
module GenericVisitor
  def method_missing *args
    if args.first.to_s =~ /visit.*/
      puts "In type: #{self.class} call to " + args.to_s
    else
      super
    end
  end
end

class SampleMethodVisitor
  include org.objectweb.asm::MethodVisitor
  include GenericVisitor
end

class SampleClassVisitor
  include org.objectweb.asm::ClassVisitor
  include GenericVisitor
  
  # we need to keep a copy of our method visitor
  def initialize args
    @method_visitor = args[:method_visitor]
  end
  
  def visit_method *args
    # The output has methods seperated with an easy-to-see string
    puts "visit method " + args.to_s + "================================"
      
    # Asm wants ClassVisitor#visitMethod to return a method visitor 
    @method_visitor
  end
end

# Loop through each .class file given on the command line
ARGV.each do |classfile|
  puts " ---------------- Reading file #{classfile}"
  f = java.io.File.new classfile
  fis = java.io.FileInputStream.new f
  
  class_reader = ClassReader.new fis
  
  v = SampleClassVisitor.new method_visitor: SampleMethodVisitor.new
  
  class_reader.accept v, 0
  
  puts
end

And here’s the output:

---------------- Reading file /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class
In type: SampleClassVisitor call to [:visit, 50, 33, "com/restphone/classSignature/SampleOne", nil, "java/lang/Object", #<#<Class:0x12690ed81>:0x28b53b32>]
In type: SampleClassVisitor call to [:visitSource, "SampleOne.java", nil]
In type: SampleClassVisitor call to [:visitField, 9, "x", "Ljava/lang/Integer;", nil, nil]
visit method [8, "<clinit>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitLineNumber, 4, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitFieldInsn, 178, "com/restphone/classSignature/SampleTwo", "y", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitFieldInsn, 179, "com/restphone/classSignature/SampleOne", "x", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 0]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "<init>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 0]
In type: SampleMethodVisitor call to [:visitMethodInsn, 183, "java/lang/Object", "<init>", "()V"]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>, 0]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 1]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "doubleTheValue", "(Ljava/lang/Integer;)Ljava/lang/Integer;", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitLineNumber, 7, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 1]
In type: SampleMethodVisitor call to [:visitMethodInsn, 182, "java/lang/Integer", "intValue", "()I"]
In type: SampleMethodVisitor call to [:visitInsn, 5]
In type: SampleMethodVisitor call to [:visitInsn, 104]
In type: SampleMethodVisitor call to [:visitMethodInsn, 184, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitInsn, 176]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 0]
In type: SampleMethodVisitor call to [:visitLocalVariable, "x", "Ljava/lang/Integer;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 1]
In type: SampleMethodVisitor call to [:visitMaxs, 2, 2]
In type: SampleMethodVisitor call to [:visitEnd]
In type: SampleClassVisitor call to [:visitEnd]

Incidentally, I used my new Eclipse plugin to send the current selection through a filter to html-escape the text.  Check it out on github.

Tuesday, August 23, 2011

Mobile market in Ireland - Android and iPhone

I'm on vacation in Ireland right now, and the mobile market here is interesting and different.

First, unlike Seattle where every other storefront sells coffee, in Ireland every other shop is a mobile phone operator.  They're everywhere. 

And the really big difference is prepaid.  I picked up a Samsung Europa for €49, no contract.  "Locked" to 3, but only if you're not capable of searching "unlock" on google.  (Yes, the number 3 is an Irish telecom company.)  For €20, the "topup" includes unlimited data for a month, unlimited 3-3 calling/texting,  and 29c/minute out of network, free on weekends.  Every tiny little shop in the country sells minutes, too, so it's trivial to buy more.

If I lived here and had even a tiny bit of patience, that same Europa goes for €39 delivered.  (And they sell for more than that on ebay.  My wife said we're on vacation, and I'm not supposed to pack 200 of them in my suitcase to resell at a profit.  Shame.  I did point this out to the young guy in the 3 shop though, and he looked thoughtful.)

It's an Android 2.2 device.  It's not great (no multitouch, small screen, slow compared to my wife's Nexus One), but it's OK.

The iPhone is a nice chunk of hardware, but it's €569 - TEN TIMES the price of an Android phone.  Yes, you can get it for free on a contract, but it's a middle-class extravagance.  If you think you might be worried about cash next month, I don't see how you buy one.

I had no idea how much cheaper Android phones were over here.  I'm not seeing a whole lot of iPhones; it's not like home.


Oh, and if you do bring an unlocked Android GSM phone with you, they'll tell you the unlimited data for €20 plan doesn't work for tethering.  Works great, though. 

Saturday, May 28, 2011

Very simple Google Spreadsheet code

I wanted to use data from a google docs spreadsheet in a project, and the sample code all talks about how to authenticate and get lists of documents for a user.

In my case, I already had a public document that I wanted to read. 

You’ll want to read the doc here first, but there were two gaps. 

  1. It wasn’t straightforward (at least for me) to figure out exactly what the URL for a public spreadsheet should be (the doc concentrates on private documents, and assumes you’re going to be iterating through a list of available spreadsheets, not going straight to a specific one).
  2. It wasn’t clear which Java classes mapped to the URLs I had.

So here’s a very simple sample, reading data from this public spreadsheet:


The output:

One
1
Two
2
Three
3

The code:

package com.banshee;

import java.io.IOException;
import java.net.URL;

import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CustomElementCollection;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.util.ServiceException;

public class SpreadsheetSucker {
  public static void main(String[] args) {
    SpreadsheetService service = new SpreadsheetService("com.banshee");
    try {
      // Notice that the url ends
      // with default/public/values.
      // That wasn't obvious (at least to me)
      // from the documentation.
      String urlString = "https://spreadsheets.google.com/feeds/list/0AsaDhyyXNaFSdDJ2VUxtVGVWN1Yza1loU1RPVVU3OFE/default/public/values";

      // turn the string into a URL
      URL url = new URL(urlString);

      // You could substitute a cell feed here in place of
      // the list feed
      ListFeed feed = service.getFeed(url, ListFeed.class);

      for (ListEntry entry : feed.getEntries()) {
        CustomElementCollection elements = entry.getCustomElements();
        String name = elements.getValue("name");
        System.out.println(name);
        String number = elements.getValue("Number");
        System.out.println(number);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ServiceException e) {
      e.printStackTrace();
    }

  }
}

Monday, May 16, 2011

Notes on Android Market for Developers session at Google I/O 2011


Notes on Android Market for Developers session at Google I/O 2011

(For Google I/O Show and Tell http://www.meetup.com/seattle-gtug/events/16361982/)

- James Moore, james@restphone.com

Session video is up at http://www.google.com/events/io/2011/sessions/android-market-for-developers.html
  • Growth
    • 400k activations/day for Android devices worldwide
    • 60% of activiations are outside USA
    • He emphasized Korea and Japan
    • Up from 100k/day YOY
    • App installs
      • 8x from 2009 -> 2010
      • 2011 as of today is slightly more than all of 2010
    • Charts around 3:08 in video
  • Apps are used by Gingerbread and Honeycomb
    • New device users == heavy consumers of applications
    • graph at 6:30
  • Recent installs of apps (trending categories)
    • Games
    • Entertainment
    • Tools
    • Media
    • Travel and local
    • Transportation
  • Trending Paid apps categories (8:46)
    • Games
    • Tools
    • Personalization
    • Productivity
    • Entertainment
    • Medical
    • Comics
    • Shopping
  • New OS versions are most of the app downloads 10:32
    • They see two versions of Android dominant over time (the two change as new releases come out)
    • Today
      • 25% 2.1
      • 75% 2.2
    • developer.android.com has lots of numbers for things like versions, screensizes
    • Major vendors committed to 18 month upgrade guarantees, announced at IO2011
  • Quick demos of (14:49)
    • Pulse News
    • Angry Birds
    • Gun Brothers
    • Talked about approaches to OS version dependencies
  • 90% of Android devices hitting the market have OpenGS 2.0
  • New market tools offer better targeting
    • Devices, geography, screensize, etc
  • Coming soon
    • Multiple apks (23:01) in June
      • One app listing
      • Different screen sizes, etc
      • Multiple apks
      • Aggregates billing, rating, comments
    • Large apks 24:23
      • 4GB total
      • 50 meg apk plus 2 x 2GB bundles downloaded separtelly
  • One-click integration with AdMob, already launched
  • Lots of new lists on Marketplace
    • Trending
    • Lists by market
    • Better ways to show all apps from a developer
  • Direct carrier billing is 50% of app sales at one (unnamed) US carrier
  • In-app upgrades are working really well


These notes are up at https://docs.google.com/document/pub?id=1mWJAHkFcP_C91opc9bOKowHye3BgMwk2G-92Mia-MUA

Tuesday, May 3, 2011

Android error: java.lang.Error: packaging failure: closure resource not found

If you're seeing errors like this when you start your Android application: You've compiled with CodePro coverage enabled. Turn it off.

Thursday, April 21, 2011

App Engine debug project gets java.lang.NoSuchMethodError: org.mortbay.thread.Timeout

I see this error sometimes when I'm starting my Google App Engine / GWT project:

Exception in thread "main" java.lang.NoSuchMethodError: org.mortbay.thread.Timeout.(Ljava/lang/Object;)V
 at org.mortbay.io.nio.SelectorManager$SelectSet.(SelectorManager.java:306)
 at org.mortbay.io.nio.SelectorManager.doStart(SelectorManager.java:223)
 at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
 at org.mortbay.jetty.nio.SelectChannelConnector.doStart(SelectChannelConnector.java:303)
 at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
 at org.mortbay.jetty.Server.doStart(Server.java:233)
 at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
 at com.google.gwt.dev.shell.jetty.JettyLauncher.start(JettyLauncher.java:565)
 at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:494)
 at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1058)
 at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:800)
 at com.google.gwt.dev.DevMode.main(DevMode.java:304)

The simple fix is to reset the SDK for App Engine, under Properties > Google > App Engine.  Change it to something other than your current SDK, then change it back.  This doesn't happen often enough for me to figure out what the real fix might be.

Monday, March 28, 2011

I have an official radio phone number!

According to Ma Bell's Officially Recommended Exchange Names, my cell phone number is "reserved for radio telephone numbers."

Found this through a comment on thedailywtf.  Hilarious.

Thursday, March 24, 2011

Bookstands for holding dev devices

I’ve just had my dev devices lying on the desk, but I decided I wanted something to hold them up.  Ordered a couple from Amazon.

I ordered the Insight and the Fellows.  I’m using the Insight regularly.  For iPhones, it’s got a nice slot in the back to slip the cable through.  I’m using a Droid as my regular Android dev device, and it’s connector is on the side, so no issues there.  Haven’t tried the Evo yet. 

For the iPhone, it’s not quite stable with the cable plugged in, so I just wrapped a rubber band around it.  Not exactly high design, but works fine.

Both of these are cheap; < $10.

I also ordered a Gorillapod, but it’s not stable enough when you’re punching stuff on the screen.  I liked it enough to keep it, though, just for its intended use (camera tripod).

Insight:

P1010709

P1010708

Fellows

P1010707

Tuesday, August 17, 2010

Problem with Eclipse/Android/Helios/XML editing

If you're seeing errors like this in Eclipse/Helios when you're editing an Android resource file, you're hitting this Eclipse bug.  The workaround is to go to Preferences > XML > XML Files > Editor and turn off Use inferred grammar in absense of DTD/Schema.
Problems occurred when invoking code from plug-in: "org.eclipse.jface".

java.lang.NullPointerException
at org.eclipse.wst.xml.core.internal.document.ElementImpl.getDefaultValue(ElementImpl.java:259)
at org.eclipse.wst.xml.core.internal.document.ElementImpl.getAttributeNS(ElementImpl.java:329)
at com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode.getShortDescription(Unknown Source)
at com.android.ide.eclipse.adt.internal.editors.ui.tree.UiModelTreeLabelProvider.getText(Unknown Source)
at org.eclipse.jface.viewers.WrappedViewerLabelProvider.getText(WrappedViewerLabelProvider.java:108)
at org.eclipse.jface.viewers.WrappedViewerLabelProvider.update(WrappedViewerLabelProvider.java:164)
at org.eclipse.jface.viewers.ViewerColumn.refresh(ViewerColumn.java:152)
at org.eclipse.jface.viewers.AbstractTreeViewer.doUpdateItem(AbstractTreeViewer.java:934)
at org.eclipse.jface.viewers.AbstractTreeViewer$UpdateItemSafeRunnable.run(AbstractTreeViewer.java:102)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
at org.eclipse.ui.internal.JFaceUtil$1.run(JFaceUtil.java:49)
at org.eclipse.jface.util.SafeRunnable.run(SafeRunnable.java:175)
at org.eclipse.jface.viewers.AbstractTreeViewer.doUpdateItem(AbstractTreeViewer.java:1014)
at org.eclipse.jface.viewers.StructuredViewer$UpdateItemSafeRunnable.run(StructuredViewer.java:481)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
at org.eclipse.ui.internal.JFaceUtil$1.run(JFaceUtil.java:49)
at org.eclipse.jface.util.SafeRunnable.run(SafeRunnable.java:175)
at org.eclipse.jface.viewers.StructuredViewer.updateItem(StructuredViewer.java:2141)
at org.eclipse.jface.viewers.AbstractTreeViewer.updateChildren(AbstractTreeViewer.java:2689)
at org.eclipse.jface.viewers.AbstractTreeViewer.internalRefreshStruct(AbstractTreeViewer.java:1867)
at org.eclipse.jface.viewers.TreeViewer.internalRefreshStruct(TreeViewer.java:721)
at org.eclipse.jface.viewers.AbstractTreeViewer.internalRefresh(AbstractTreeViewer.java:1842)
at org.eclipse.jface.viewers.AbstractTreeViewer.internalRefresh(AbstractTreeViewer.java:1799)
at org.eclipse.jface.viewers.AbstractTreeViewer.internalRefresh(AbstractTreeViewer.java:1785)
at org.eclipse.jface.viewers.StructuredViewer$7.run(StructuredViewer.java:1487)
at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1422)
at org.eclipse.jface.viewers.TreeViewer.preservingSelection(TreeViewer.java:403)
at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1383)
at org.eclipse.jface.viewers.StructuredViewer.refresh(StructuredViewer.java:1485)
at org.eclipse.jface.viewers.ColumnViewer.refresh(ColumnViewer.java:537)
at org.eclipse.jface.viewers.StructuredViewer.refresh(StructuredViewer.java:1444)
at org.eclipse.jface.viewers.ContentViewer.setContentProvider(ContentViewer.java:252)
at org.eclipse.jface.viewers.StructuredViewer.setContentProvider(StructuredViewer.java:1641)
at org.eclipse.jface.viewers.AbstractTreeViewer.setContentProvider(AbstractTreeViewer.java:2317)
at org.eclipse.jface.viewers.TreeViewer.setContentProvider(TreeViewer.java:972)
at com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock.changeRootAndDescriptors(Unknown Source)
at com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock.createTreeViewer(Unknown Source)
at com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock.createMasterPart(Unknown Source)
at org.eclipse.ui.forms.MasterDetailsBlock.createContent(MasterDetailsBlock.java:161)
at org.eclipse.ui.forms.MasterDetailsBlock.createContent(MasterDetailsBlock.java:142)
at com.android.ide.eclipse.adt.internal.editors.resources.ResourcesTreePage.createFormContent(Unknown Source)
at org.eclipse.ui.forms.editor.FormPage$1.run(FormPage.java:152)
at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
at org.eclipse.ui.forms.editor.FormPage.createPartControl(FormPage.java:150)
at org.eclipse.ui.forms.editor.FormEditor.pageChange(FormEditor.java:471)
at com.android.ide.eclipse.adt.internal.editors.AndroidEditor.pageChange(Unknown Source)
at org.eclipse.ui.part.MultiPageEditorPart.setActivePage(MultiPageEditorPart.java:1067)
at org.eclipse.ui.forms.editor.FormEditor.setActivePage(FormEditor.java:603)
at com.android.ide.eclipse.adt.internal.editors.AndroidEditor.selectDefaultPage(Unknown Source)
at com.android.ide.eclipse.adt.internal.editors.AndroidEditor.addPages(Unknown Source)
at org.eclipse.ui.forms.editor.FormEditor.createPages(FormEditor.java:138)
at org.eclipse.ui.part.MultiPageEditorPart.createPartControl(MultiPageEditorPart.java:348)
at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:670)
at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:465)
at org.eclipse.ui.internal.WorkbenchPartReference.getPart(WorkbenchPartReference.java:595)
at org.eclipse.ui.internal.EditorReference.getEditor(EditorReference.java:289)
at org.eclipse.ui.internal.WorkbenchPage.busyOpenEditorBatched(WorkbenchPage.java:2863)
at org.eclipse.ui.internal.WorkbenchPage.busyOpenEditor(WorkbenchPage.java:2768)
at org.eclipse.ui.internal.WorkbenchPage.access$11(WorkbenchPage.java:2760)
at org.eclipse.ui.internal.WorkbenchPage$10.run(WorkbenchPage.java:2711)
at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2707)
at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2691)
at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2682)
at org.eclipse.ui.ide.IDE.openEditor(IDE.java:651)
at org.eclipse.ui.ide.IDE.openEditor(IDE.java:610)
at org.eclipse.jdt.internal.ui.javaeditor.EditorUtility.openInEditor(EditorUtility.java:365)
at org.eclipse.jdt.internal.ui.javaeditor.EditorUtility.openInEditor(EditorUtility.java:168)
at org.eclipse.jdt.ui.actions.OpenAction.run(OpenAction.java:229)
at org.eclipse.jdt.ui.actions.OpenAction.run(OpenAction.java:208)
at org.eclipse.jdt.ui.actions.SelectionDispatchAction.dispatchRun(SelectionDispatchAction.java:274)
at org.eclipse.jdt.ui.actions.SelectionDispatchAction.run(SelectionDispatchAction.java:250)
at org.eclipse.jdt.internal.ui.packageview.PackageExplorerActionGroup.handleOpen(PackageExplorerActionGroup.java:373)
at org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart$4.open(PackageExplorerPart.java:526)
at org.eclipse.ui.OpenAndLinkWithEditorHelper$InternalListener.open(OpenAndLinkWithEditorHelper.java:48)
at org.eclipse.jface.viewers.StructuredViewer$2.run(StructuredViewer.java:845)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
at org.eclipse.ui.internal.JFaceUtil$1.run(JFaceUtil.java:49)
at org.eclipse.jface.util.SafeRunnable.run(SafeRunnable.java:175)
at org.eclipse.jface.viewers.StructuredViewer.fireOpen(StructuredViewer.java:843)
at org.eclipse.jface.viewers.StructuredViewer.handleOpen(StructuredViewer.java:1131)
at org.eclipse.jface.viewers.StructuredViewer$6.handleOpen(StructuredViewer.java:1235)
at org.eclipse.jface.util.OpenStrategy.fireOpenEvent(OpenStrategy.java:264)
at org.eclipse.jface.util.OpenStrategy.access$2(OpenStrategy.java:258)
at org.eclipse.jface.util.OpenStrategy$1.handleEvent(OpenStrategy.java:298)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:3776)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1367)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1390)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1375)
at org.eclipse.swt.widgets.Widget.notifyListeners(Widget.java:1187)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3622)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3277)
at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2629)
at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2593)
at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2427)
at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:670)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:663)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:115)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:369)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:619)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:574)
at org.eclipse.equinox.launcher.Main.run(Main.java:1407)

Tuesday, June 29, 2010

A proguard builder for Scala + Android

I just put up code for an Eclipse builder to make it simpler to build Scala projects for Android.

http://github.com/banshee/AndroidProguardScala

It's still pretty rough, but it gets the job done.

Saturday, June 19, 2010

Eclipse plug-ins not showing suggested templates

If you’re new to developing Eclipse templates, and you’re wondering why you’re not seeing a list of templates when you create your new project, make sure you check “This plug-in will make contributions to the UI” in this dialog:
image
If you don’t check that, you’ll see:
image
Instead of:
image

Thursday, June 10, 2010

Comparing Microsoft and Google

Interesting comment on Microsoft vs Google (what it's like to work there) on the
Mini-Microsoft blog.


Look for the comment starting with:

>> And how do you compare GOOG to MSFT?

It's not black and white, there are advantages and disadvantages to both.

http://minimsft.blogspot.com/2010/05/thoughts-on-wrapping-up-microsofts-fy10.html?commentPage=2

Wednesday, June 9, 2010

Fail Whale

Just got the Twitter Fail Whale - haven't seen that for a long time:

Tuesday, June 8, 2010



If you're trying to use jruby on rails, and you see an error like this:
GEM_HOME=~/.jrubygem jruby -S rake db:create:all
(in /private/tmp/fnord)
!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.
rake aborted!
no such file to load -- mysql

(See full trace by running task with --trace)


The solution is to run script/generate jdbc:

GEM_HOME=~/.jrubygem jruby -S script/generate jdbc
      exists  config/initializers
      create  config/initializers/jdbc.rb
      exists  lib/tasks
      create  lib/tasks/jdbc.rake
See Nick Sieger's blog for more detail.

Tuesday, May 25, 2010

Windows 7 Mobile Operating Thingy for Whatsits - does anyone care?

I was at Google IO last week.  Talk about iPhones was everywhere - people thinking about how the Android vs iPhone battle was going to play out.

Windows 7 Mobile Operating Thingy for Whatsits (or whatever they're calling it these days) wasn't talked about.  Not at all.  Zero. Nada.

The Kin?  Again, nothing.  Crickets.

I hear there was trivial shakeup at Microsoft today.  Whatever.

Do the people at Microsoft understand how bad things are?  Sure doesn't seem like it.

Monday, May 24, 2010

Android carriers and phones - Sprint, TMobile, Verizon, ATT, Droid, Evo, iPhone, Nexus One

We're just finishing up a trip to San Francisco, Los Angeles and San Diego. Between me and my fiancee we're carrying around four cell phones (thanks to giveaways at Google IO):

  • Droid/Verizon
  • HTC Evo/Sprint
  • iPhone 3G/ATT
  • Nexus One/TMobile (my fiancee's phone)
Our reactions to the carriers and phones so far:

Droid/Verizon: works great, works everywhere, but you need a forklift to carry this thing around. The Droid is clearly an engineering prototype that someone from marketing stole from the lab. It's just not a shipping product, no way, no how.

However, since I'm a geek, carrying around the engineering prototype hasn't been that bad.  When I was carrying around the Droid and the iPhone 3G, I'd almost always pull out the Droid.  I'd really only pull out the iPhone for UrbanSpoon.

HTC Evo/Sprint: The new hotness.  The screen is bigger than my laptop.  It's fast.   It's got some lame software on it called "Sense" - sometime soon I'll need to figure out how to disable it, but it's only mildly annoying so far.

Sprint has been surprisingly good - no coverage problems in urban areas so far, and very good coverage on the train between LA and San Diego (with some dropouts where geography was pretty obviously terrible - Camp Pendleton for one).

iPhone 3G/ATT: I'd like to tell you about ATT's coverage, but I almost never pull the iPhone out of my pocket.  It feels archaic at this point.

Nexus One/TMobile: Very nice phone, but as far as we can tell TMobile isn't actually in the business of providing wireless connections to cell phones with any sort of reasonable coverage.  Just pathetic.  TMobile isn't worth considering as a provider.

Wednesday, May 12, 2010

Android - loading a javascript file from a web page needs internet permission

If you're trying to include javascript in an android.webkit.WebView view with a standard script tag like this:
<script language="javascript" src="demo.js" />
You need to add internet permissions to your manifest:
<uses-permission android:name="android.permission.INTERNET" />
Doesn't seem to matter that the file is local.

Tuesday, April 27, 2010

Liking the Droid a bit more

After using the Droid for a bit, I'm feeling more charitable.

Just have to think of it as an engineering prototype.  It's not really a device you'd expect people to pay money for - it's far too clunky for that.  But as a limited production device just for developers, it's fine.

I'm carrying it and my iphone around now (it's a pain), and I find that I pull out the Droid far more often.  The web browsing experience is just that much better than the iPhone.

Thursday, April 22, 2010

New Droid arrived

Got my new Droid a few minutes ago.

First impression: the thing feels like a tank.  Very industrial.  Not at all a warm and fuzzy feeling like the Nexus One or my iPhone.

And the slideout keyboard is the definition of epic fail.  Useless, probably adds quite a bit of weight and complexity to the phone.  Just wrong, wrong, wrong.  This phone should never have left the lab.

I suspect that I did this in the wrong order.  You need to experience a Droid before you play with a Nexus One.  Had I done that, I might have been more impressed.

It's good to have a Droid though, since they're apparently the most popular Android device.  Good to have around for testing.  But I pity the people who have to use this as their primary device.

Friday, April 2, 2010

ipad - thumbs down

I'm on a ski trip with a friend (teenager, from San Diego, the cultural capital of the world). His unsolicited comments on the ipad:

It's just a giant ipod touch.   Go buy yourself a computer with that. Like seriously what's the point of that? Don't buy the ipad. Piece of ..."


I'm with him. If the thing were seriously less expensive, it'd be interesting. It's not.


Now, I should probably add that he's now chanting "looser" since I'm taking too long to type up this entry, so maybe I'm not so fond of his opinions after all...

Monday, March 22, 2010

jruby and mkdir problems with gems

If you see an error like this when you're installing gems:
GEM_HOME=/Users/james/.jrubygem GEM_PATH=/Users/james/.jrubygem jruby -S gem install -V activerecord-jdbcmysql-adapter rails

ERROR:  While executing gem ... (SystemCallError)
  Unknown error - mkdir failed
Make sure you don't have a ~/.gem directory with bad permissions. jruby wanted to use that directory for something even though I had specified a different GEM_HOME.

Sunday, March 14, 2010

Sunday, March 7, 2010

HTML with the F# question mark operator

I needed to create some HTML from F#.  I wanted something where the F# looked something the the results, and I didn’t have any need for anything other than the basics (tags, attributes, text contents).

I had seen a few posts talking about the question mark operator and hadn’t used it yet.  It seemed like the obvious candidate – it means you can put tags into code without writing a whole lot of support for each and every tag name you’re going to need.

What I ended up with is syntax that looks like this:

let h = com.restphone.Qml.Builder()

let exampleWithATable =
  let a =
    h?table <- [
      h?tr <- [
        h?td <- "one"
        h?td <- "two"
      ]
      h?tr
    ]
  printExample "a table" a
// a table
// -----
// <table>
//   <tr>
//     <td>one</td>
//     <td>two</td>
//   </tr>
//   <tr />
// </table>
// 

The elements after the question marks are just turned into strings then passed to a function that turns them into nodes in the HTML tree. Nesting nodes uses the <- operator followed by a list of items that can be:

  • Strings get turned into content
  • Two-element tuples are turned into key-value attributes
  • Nodes (using the same h?sometag syntax)

I like the way the syntax ends up looking quite a bit like the HTML it’s producing as the final output.

A couple more examples:

let exampleWithAttributes = 
  let a1 = 
    h?ol <- [
      h?li <- [
        "class", "formattedList"                   // Two-element tuples are attributes
        "something", "something & else"           
        {name = "class"; value = "listItemStyle"}  // Or you can use an actual Attribute object
        "this should have escapes: & < >"          // content will be escaped
      ];
      h?li <- "second"
    ]
  printExample "list items with content and attributes" a1
// list items with content and attributes
// -----
// <ol>
//   <li class="formattedList" something="something &amp; else" class="listItemStyle">this should have escapes: &amp; &lt; &gt;</li>
//   <li>second</li>
// </ol>
// 

let exampleWithASpanInContent =
  let b =
    h?foo <- [
      "content text"
      h?span <- "something in a span"
      "more text"
    ]
  printExample "content containing a span" b
// content containing a span
// -----
// <foo>content text<span>something in a span</span>more text</foo>
// 

And here’s the code:

namespace com.restphone.Qml

open System.Xml

// Name-value pairs
type Attribute =
  {name: string;
   value: string}

type Element =
  {tag: string;                   // The html tag; table, p, etc
   attributes: Attribute list;
   children: Node list}
and Node =
  | Element of Element
  | Content of string

type Builder() =
  static let emptyAttribute = {name = ""; value = ""}

  static let tupleToAttribute (t: System.Tuple<string, string>) =
    {emptyAttribute with name = t.Item1; value = t.Item2}

  static let builder(builder, tag, things: obj list) =
    let rec appendThings (element: Element) (xs: obj list) =
      match xs with
      | (:? Element as nextElement)::t -> appendThings {element with children = (List.append element.children [Element nextElement])} t
      | (:? string as content)::t -> appendThings {element with children = (List.append element.children [Content content])} t
      | (:? List<Attribute> as attrs)::t -> appendThings {element with attributes = List.append element.attributes attrs} t
      | (:? Attribute as attr)::t -> appendThings element ([attr] :> obj::t)
      | (:? System.Tuple<string, string> as p)::t -> appendThings element ((tupleToAttribute p) :> obj::t)
      | [] -> element
      | x -> element
    appendThings ((?) builder tag) things
  
  static let printAttribute (x: System.Xml.XmlWriter) (attr: Attribute) =
    x.WriteAttributeString(attr.name, attr.value)
     
  static let printAttributes x attrs =
    List.iter (printAttribute x) attrs

  static let rec printContent (x: System.Xml.XmlWriter) s =
    x.WriteString s

  static let rec printElements x e =
    let pe = function
      | Content c -> printContent x c
      | Element el -> printElement x el
    List.iter pe e
  and
    printElement (x: System.Xml.XmlWriter) e =
      x.WriteStartElement e.tag
      printAttributes x e.attributes
      printElements x e.children
      x.WriteEndElement ()
    
  static member (?) (a: Builder, tag) =
    {tag = tag; attributes = []; children = []}
  
  static member (?<-) (a: Builder, tag, things: obj list) = builder(a, tag, things)
  static member (?<-) (a: Builder, tag, content: string) = builder(a, tag, [content])
  static member (?<-) (a: Builder, tag, attributePair: string * string) = builder(a, tag, [attributePair])
  static member (?<-) (a: Builder, tag, attr: Attribute) = builder(a, tag, [attr])
  static member (?<-) (a: Builder, tag, e: Element) = builder(a, tag, [e])
      
  static member write elements x =
    Seq.iter (printElement x) elements

  static member ElementSeqToString (h: Element seq) = 
    let sw = new System.IO.StringWriter()
    let xtw = new XmlTextWriter(sw)
    xtw.Formatting <- Formatting.Indented
    Builder.write h xtw |> ignore
    sw.ToString()

  static member ElementToString (h: Element) = 
    Builder.ElementSeqToString [h]

Sunday, December 13, 2009

Sunday, October 25, 2009

Ninite - bulk installer, potentially useful

This looks useful - it's a bulk installer for a bunch of things I use.  Windows 7 upgrades on a couple machines are in my very near future, so hopefully this will save some time.

http://ninite.com/

Tuesday, September 29, 2009

F# running on the iPhone

I tried F# on the iPhone (via MonoTouch) tonight.  The trivial hello-world code seemed to work just fine.  I used standalone to build the (trivial) fsharp code:

type Foo() =
  do System.Console.WriteLine("This output brought to you by the note F#");
mono ../FSharp-1.9.6.16/bin/fsc.exe -a Foo.fs

And added a line to instantiate a Foo in FinishedLaunching().

Loaded the resulting dll into MonoDevelop, along with FSharp.Core.dll, and it just worked.  Or at least it worked in the simulator, haven't tried the real device yet.

fsharponiphone

Friday, September 18, 2009

Sequence of positions in a sequence (F#)

Steve Horsfield's blog had a post on fold functions that cared about the beginning, middle and ends of the sequence they were operating on.  I think there’s a preprocessing step that simplifies the problem – transform the sequence into a seq<Position<'t>>, where Position tells you if the object is at the beginning, middle, or end of the sequence:

type Position<'t> =
| Beginning of 't
| Middle of 't
| End of 't
| Only of 't

let rec sequenceOfPositionsWithLazyList(existing: LazyList<'t>) =
  seq {
    let rec elementsAfterBeginning(s) =
      seq {
        match s with
        | LazyList.Cons(h, LazyList.Nil) -> yield End(h); ()
        | LazyList.Cons(h, t) -> yield Middle(h); yield! elementsAfterBeginning(t)
        | _ -> ()
      }
    match existing with
      | LazyList.Cons(h, LazyList.Nil) -> yield Only(h); ()
      | LazyList.Cons(h, t) -> yield Beginning(h); yield! elementsAfterBeginning(t)
      | LazyList.Nil -> ()
  }
  
let sequenceOfPositions(existing: seq<'t>) = sequenceOfPositionsWithLazyList(LazyList.of_seq existing)
  
printfn "%A" (sequenceOfPositions [1;2;3])
printfn "%A" (sequenceOfPositions [1;3])
printfn "%A" (sequenceOfPositions [1])
printfn "%A" (sequenceOfPositions [])

The output is:

seq [Beginning 1; Middle 2; End 3]
seq [Beginning 1; End 3]
seq [Only 1]
seq []

I think that makes following steps much easier to write.

Friday, July 10, 2009

error: 'GDataInputStreamLogger' undeclared (first use in this function)

If you're seeing errors like this:

/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m: In function '-[GDataHTTPFetcher(GDataHTTPFetcherLogging) logCapturePostStream]':
/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m:797: error: 'GDataInputStreamLogger' undeclared (first use in this function)
/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m:797: error: (Each undeclared identifier is reported only once
/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m:797: error: for each function it appears in.)
/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m:800: error: expected expression before ')' token
/Users/jamesmoore/dev/mba/gdata170/Source/Networking/GDataHTTPFetcherLogging.m:804: error: expected expression before ')' token

Set this preprocessor macro: STRIP_GDATA_FETCH_LOGGING=0.

(TBD:  An actual explanation.  Unfortunately, it's not high on my priority list right now.)

Monday, June 15, 2009

Merging two sequences in F# using LazyList

Claudio Cherubino  had a post about solving one of the standard Amazon interview questions, and it prompted me to finally figure out how to use sequence expressions and LazyList in F#.

The problem is to merge two arrays into a third array in O(n) time.  I like the F# solution using sequences and pattern matching - it feels like a nice way to write this sort of thing. 
type MergeWithLazyLists() =
static member merge(f, x: LazyList<'t>, y: LazyList<'t>) =
seq {
match x, y with
| LazyList.Cons(xh, xt), LazyList.Cons(yh, yt) when f xh yh ->
yield xh
yield! MergeWithLazyLists.merge(f, xt, y)
| LazyList.Cons(xh, xt), LazyList.Cons(yh, yt) ->
yield yh
yield! MergeWithLazyLists.merge(f, x, yt)
| LazyList.Nil, LazyList.Cons(_, _) -> yield! y
| LazyList.Cons(_, _), LazyList.Nil -> yield! x
| LazyList.Nil, LazyList.Nil -> ()
}

static member merge(f, x: seq<'t>, y: seq<'t>) =
MergeWithLazyLists.merge(f, (LazyList.ofSeq x), (LazyList.ofSeq y))

let result = MergeWithLazyLists.merge((fun x y -> x < y), (seq {2..6}), (seq {1..7..15}))

printfn "%A" (Seq.toArray result)

A sequence in F# is anything that implements IEnumerable<'t>, so arrays and lists both work just fine.

(An earlier version of this post used the older names for some methods – to_seq, to_array.  I updated this to the new methods in 1.9.7.8)

Wednesday, May 27, 2009

Recovering Linux RAID drives

I had a couple drives that used to be part of a Linux RAID setup.  The easiest way I've found to grab the data off them is TestDisk - it's a handy tool that will copy files off a drive that was part of a RAID array.  Even better, you can run it from Linux and Windows.  In my case, my new main machine is a Vista box, so I don't have to figure out how to get my VMware instance of Ubuntu to read drive image files on the Windows box.

I found TestDisk through this article: Mounting Linux drive images

Tuesday, April 21, 2009

GTM test suite for the iPhone - figuring out why it was hanging on startup

[Sorry about the formatting problems. This was posted to my old blog, and I found it again when I was doing a google search for the topic. The old blog posts are lying around somewhere, but I'm lazy so I'll just cut-and-paste from the google cache...] I'm trying to use the GTM test suite, and it's just hanging. The build window shows:
    2008-11-21 09:23:46.996 UnitTests[1794:10b] CFPreferences: user home directory at /Users/jamesmoore/dev/TestPlatform/build/TestPlatform.build/Debug-iphonesimulator/UnitTests.build/iPhone Simulator User Dir is unavailable. User domains will be volatile.    2008-11-21 09:23:47.012 UnitTests[1794:10b] Unable to send CPDistributedMessagingCenter message named SBRemoteNotificationClientStartedMessage to com.apple.remotenotification.server: (ipc/send) invalid destination port
Starting under the debugger (using option-cmd-Y to skip running the test harness shell script) isn't very informative:
(gdb) bt#0  0x964954a6 in mach_msg_trap ()#1  0x9649cc9c in mach_msg ()#2  0x956fd0ce in CFRunLoopRunSpecific ()#3  0x956fdcf8 in CFRunLoopRunInMode ()#4  0x31699d38 in GSEventRunModal ()#5  0x31699dfd in GSEventRun ()#6  0x30a5dadb in -[UIApplication _run] ()#7  0x30a68ce4 in UIApplicationMain ()#8  0x00002a39 in main (argc=1, argv=0xbffff060) at /Users/jamesmoore/dev/TestPlatform/../google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestMain.m:30(gdb)
All that looks fine. So, did the tests run at all? GTMIphoneUnitTestMain.m does:
  int retVal = UIApplicationMain(argc, argv, nil, @"GTMIPhoneUnitTestDelegate");
So I set a breakpoint on the first line of applicationDidFinishLaunching, on the call to runTests:
- (void)applicationDidFinishLaunching:(UIApplication *)application {  [self runTests];
And sure enough, it's not hit. So my next suspect is the app delegate for the regular application - inthis case, TestPlatformAppDelegate. Set a breakpoint in itsapplicationDidFinishLaunching, start up, and sure enough that's what'srunning. Why? Turns out I had added MainWindow.xib to the unit test target. For the regular (non-unit-test)application, MainWindow.xib has an instance of TestPlatformAppDelegate.The TestPlatformAppDelegate is attached to the File's Owner delegate.When the nib file is instantiated, it creates theTestPlatformAppDelegate, then sets the File's Owner delegate to be thenewly instantiated TestPlatformAppDelegate. That overwrites theinstance of GTMIPhoneUnitTestDelegate that used to be there, and no tests will run. The next question is can something spit out a warning if this happens? Off the top of my head, I don't know who gets called when nib files areloaded. I'm sitting at a debugger, so I slap in a new init method toGTMIPhoneUnitTestDelegate, set a breakpoint on it, and restart. Here'sthe stack:
#0  -[TestPlatformAppDelegate init] (self=0x44fb80, _cmd=0x96d6c858) at /Users/jamesmoore/dev/TestPlatform/Classes/TestPlatformAppDelegate.m:19#1  0x30c2ca67 in -[UIClassSwapper initWithCoder:] ()#2  0x922a4940 in _decodeObjectBinary ()#3  0x922a42b5 in _decodeObject ()#4  0x30c2c615 in -[UIRuntimeConnection initWithCoder:] ()#5  0x922a4940 in _decodeObjectBinary ()#6  0x922a63cd in -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] ()#7  0x922a6849 in -[NSArray(NSArray) initWithCoder:] ()#8  0x922a4940 in _decodeObjectBinary ()#9  0x922a42b5 in _decodeObject ()#10 0x30c2bbeb in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:] ()#11 0x30c2dcf8 in -[NSBundle(NSBundleAdditions) loadNibNamed:owner:options:] ()#12 0x30a5df99 in -[UIApplication _loadMainNibFile] ()#13 0x30a65f42 in -[UIApplication _runWithURL:] ()#14 0x922ce5ee in __NSFireDelayedPerform ()#15 0x956fdb45 in CFRunLoopRunSpecific ()#16 0x956fdcf8 in CFRunLoopRunInMode ()#17 0x31699d38 in GSEventRunModal ()#18 0x31699dfd in GSEventRun ()#19 0x30a5dadb in -[UIApplication _run] ()#20 0x30a68ce4 in UIApplicationMain ()#21 0x00002a39 in main (argc=1, argv=0xbffff060) at /Users/jamesmoore/dev/TestPlatform/../google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestMain.m:30
The interesting points look like:
#10 0x30c2bbeb in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:] ()#12 0x30a5df99 in -[UIApplication _loadMainNibFile] ()
Does one of those have something to hook into? I start reading doc. The Resource Programming Guide has lots ofinformation about nib files, but the only thing I find that seemsrelevant is the bit where it points out that nib instantiation is goingto use setValue:forKey: to set the fields. To see what happens there, I added this to GTMIphoneUnitTestMain.m:
@interface GuardedUIApplication : UIApplication@end @implementation GuardedUIApplication- (void)setValue:(id)value forKey:(NSString *)key{ NSLog(@"Current value for key |%@| is |%@|", key, value); [super setValue: value forKey: key];}@end
And changed the call to UIApplicationMain to use GuardedUIApplication instead of the default UIApplication:
  int retVal = UIApplicationMain(argc, argv, @"GuardedUIApplication", @"GTMIPhoneUnitTestDelegate");
Running it again, I see that the call I expected does in fact happen:
2008-11-21 10:43:09.966 UnitTests[2841:20b] Current value for key |delegate| is ||
Something like this might be a solution:
@interface GuardedUIApplication : UIApplication@end@implementation GuardedUIApplication- (void)setValue:(id)value forKey:(NSString *)key{ NSLog(@"Existing value for key |%@| is |%@|", key, [self valueForKey: key]); NSLog(@"New value for key |%@| is |%@|", key, value); if ([key isEqualToString: @"delegate"] && ![value isKindOfClass: [GTMIPhoneUnitTestDelegate class]]) {  NSException *e = [NSException failureInFile: [NSString stringWithCString: __FILE__]             atLine: __LINE__          withDescription: @"The app delegate must be a GTMIPhoneUnitTestDelegate.  Is MainWindow.nib attempting to use a different application delegate?"];  [e raise]; } [super setValue: value forKey: key];}@end

Monday, April 13, 2009

Objective-C basics for C programmers

Objective-C is C

First, if you want to learn Objective-C and you don’t know C yet, go learn C.  Come back when you’re done.  Objective-C is a set of addons to C – you must know C first.  C is a small language, and it won’t take you that long.  (Learning to use C well is a different problem, of course.)

What to read

The Apple doc for Objective-C is pretty good, but it gets bogged down in places because it's not assuming you already know C.  It's also written as if Objective-C were a new language, instead of just some extra bits on top of C.

(And Chapter 11 of that Apple doc tosses in this shocker: C++ is also included in Objective-C.  It's not usually necessary to use C++ on the iPhone, but it does mean you can use lots of existing C++ code if you need it.)

This article is a stripped-down intro to thinking of Objective-C basics in terms of what they are in C.

Square braces are just macros

The best way to think about Objective-C syntax is that it’s just a new flavor of preprocessor for C.  There are some basic things you care about that are going to spit out normal C, eventually:
  • Messages.  They’re sent using square braces like this: [targetObject theMessage]
  • Class definitions.  They’re blocks that start with @interface @implementation @protocol and end with @end.
  • Selectors.  Selector is the Objective-C term for what most languages call “messages.”  They’re what you send to objects.  (Thinking of them as method invocations is mostly wrong and will get you in trouble later on.)  You’ll see selectors used like this: @selector(theMessage) and in square braces like this: [targetObject theMessage] - that’s just syntatic sugar.  They’re going to compile down to the same thing.

Classes

Classes are structs with some extra fields tacked on.  Here's a trivial class declaration:

@interface SmallClass : NSObject
- (void) helloWorld;
@end

This lays out the class for the compiler.  It allows the compiler to give you useful warnings if you try to send the helloWorld message to this class in the wrong way (so you'll get a warning if you try to call it with a parameter, for example).  It adds a field that's a pointer to the class itself, so at runtime you can dereference the pointer to the class type to figure out which functions correspond to which selectors.   (And yes, this implies that at runtime a class can change its type.  That's nasty, and is used by things like key-value observing.)

The implementation for this would be:

@implementation SmallClass
- (void) helloWorld
{
  NSLog(@"Hello world.");
}
@end
     
The compiler turns this into a helloWorld function that looks something like:

void helloWorldFunctionImplementation (SmallClass *self) {
  NSLog(@"Hello world");
}

To create an instance of SmallClass and send it the helloWorld selector, you'd use the the square brackets syntax:

SmallClass *sm = [[[SmallClass alloc] init] autorelease];
[sm helloWorld];


(For now, ignore the alloc/init/autorelease bits - they're memory management on the iPhone.)

Square brackets are just syntatic sugar that makes sending selectors to objects more pleasant.  What you end up with after square-bracket-procesing is a call to the function objc_msgSend(receiver, selector).  Calling objc_msgSend without the square brackets would do exactly the same thing:

objc_msgSend(sm, @selector(helloWorld));  // same as [sm helloWorld]

Or the same thing in a function:

void sendHelloWorldByHand (SmallClass *target)
{
  objc_msgSend(target, @selector(helloWorld));
}

Notice that the call to @selector(helloWorld) couldn't care less about whether or not there's a method called helloWorld implemented by that object.  It's completely irrelevant - selectors just get translated to numbers by the compiler, and they're not tied to classes in any way.  @selector(helloWorld) becomes an int inside the compiler, and you can send that int to any instance of any class.  Whether or not the target does anything interesting with that message is an unrelated issue.

To put it another way: selectors aren't tied to particular objects; the selector for helloWorld on SmallClass is the same selector that you might send to AnotherClassIHaventImplementedYet.  Selectors are just a lookup mechanism so humans don't have to remember numbers - they get informative tokens to put in code.  But they're just going to turn into numbers when your code is running.

(And you should notice that the Apple doc says not to call objc_msgSend by hand, although I suspect this is widely ignored.)

Sending messages

objc_msgSend(target, selector) goes through these steps to figure out what to call:
  1. Looking at the hidden field in the struct that points to the class of the object. 
  2. Call the function matching the selector.  Classes have lookup tables that specify the selector-to-function mapping, and if there's a matching selector, that's what's called.  If there's no matching selector, check the parent of the class, and so on until you get to the root class. 
  3. If there's no match for the selector, call forwardInvocation:.
#3 is important – it means that you cannot tell by looking at an object's @interface code whether or not it handles a selector.  That decision can't really be made until you're actually sending the selector to the object.  (And since forwardInvocation: is free to do anything it feels like with a selector, there's no guarantee that a selector will do anything the same way twice.)