text:
layout: post
title: "Sandi Metz's TRUE in Action"
date: 2015-02-16T12:32:45-06:00
author: Ian Whitney
---

[Last week](http://designisrefactoring.com/2015/02/08/introducing-sandi-metz-true/) I introduced [Sandi Metz](http://www.sandimetz.com)'s TRUE heuristic and promised that this week we'd see in in action[\*](#action). Let's jump right in.

Sandi, along with [Katrina Owen](https://twitter.com/kytrinyx), is working on a [book about OO Design based on the "99 Bottles of Beer" song](http://signup.99bottlesbook.com). If that coding problem is good enough for them, then it's good enough for me! Exercism offers it as [one of their problems](https://github.com/exercism/x-common/blob/master/beer-song.yml) as well, so we already have [a working test suite](https://github.com/exercism/xruby/blob/master/beer-song/beer_song_test.rb).

Just hacking code together, I ended up with this solution.

{% highlight ruby %}
class BeerSong
  def verses(first_verse, last_verse)
    verses = (last_verse..first_verse).to_a.reverse
    verses.inject("") {|ret, n| ret += "#{verse(n)}\n" }
  end

  def verse(number)
    if number == 2
      "#{number} bottles of beer on the wall, #{number} bottles of beer.\n" +
      "Take one down and pass it around, #{number - 1} bottle of beer on the wall.\n"
    elsif number == 1
      "#{number} bottle of beer on the wall, #{number} bottle of beer.\n" +
      "Take it down and pass it around, no more bottles of beer on the wall.\n"
    elsif number == 0
      "No more bottles of beer on the wall, no more bottles of beer.\n" +
      "Go to the store and buy some more, 99 bottles of beer on the wall.\n"
    else
      "#{number} bottles of beer on the wall, #{number} bottles of beer.\n" +
      "Take one down and pass it around, #{number - 1} bottles of beer on the wall.\n"
    end
  end

  def sing
    verses(99,0)
  end
end
{% endhighlight %}

Now that our tests pass we can ask, "How TRUE is our code?" That will be easier to determine in the context of some new features:

- California is clamoring for Beer Song as a Service (BSaaS), but they want it about wine
- Because wine is stronger, the full song should start with 20 bottles, not 99
- Also, they will bring up more wine from the cellar, not buy it at a store

### Transparent

#### It is easy to see the code's function and the effect of a change

`sing` is pretty transparent. `verses` has a bit of rigmarole to handle Ruby's ranges only being ascending, not descending. But it's idiomatic enough that I understand it. The `verse` method isn't as transparent as I'd like. Its if/elsif/else structure uses some magic numbers and also makes it hard to see the differences between the cases. But its function isn't totally opaque.

Is the code we need to change to implement these features transparent to us? I think so. In a new `WineSong` class we'd change all instances of the word "beer" to "wine" and we need to change the special "no bottles" verse to say that they go to the cellar
Then we'd change `sing` need to call `verses(20,0)`.

But then, oops, we see that the 99 is *also* in the special "no bottles" verse, so we have to change it there. So maybe that change is not as transparent as we thought.

### Reasonable

#### The effort to make a change reflects its complexity

All of these features are very low-complexity changes. Do they all take the same amount of effort? Not really. The cellar wording requires us to change a couple of words, easy. Going from 99 verses to 20 requires a change to a method and to the wording, so is a little tougher. But changing "beer" to "wine" requires us to change the code in 12 places. Yes, you can use Find and Replace (or your tool of choice) to make that change in one command. But you still end up changing the code in 12 places for a one word change. That seems excessive.

So, for the changes we want to make, our code is not entirely Reasonable.

### Usable

#### The code can be used in other contexts

This one is pretty clear. We have code that works in a Beer context, but it requires multiple changes to work in a Wine context. The only unchanged code is the `verses` method. The other two methods in BeerSong needed changes to work in the Wine context. This code is not very Usable.

### Exemplary

#### The code serves as an example for future code

Let's look at our we'd implement WineSong without changing BeerSong.

{% highlight ruby %}
class WineSong
  def verses(first_verse, last_verse)
    verses = (last_verse..first_verse).to_a.reverse
    verses.inject("") {|ret, n| ret += "#{verse(n)}\n" }
  end

  def verse(number)
    if number == 2
      "#{number} bottles of wine on the wall, #{number} bottles of wine.\n" +
      "Take one down and pass it around, #{number - 1} bottle of wine on the wall.\n"
    elsif number == 1
      "#{number} bottle of wine on the wall, #{number} bottle of wine.\n" +
      "Take it down and pass it around, no more bottles of wine on the wall.\n"
    elsif number == 0
      "No more bottles of wine on the wall, no more bottles of wine.\n" +
      "Go to the cellar and bring up some more, 20 bottles of wine on the wall.\n"
    else
      "#{number} bottles of wine on the wall, #{number} bottles of wine.\n" +
      "Take one down and pass it around, #{number - 1} bottles of wine on the wall.\n"
    end
  end

  def sing
    verses(20,0)
  end
end
{% endhighlight ruby %}

Because of the way BeerSong was structured we had to duplicate 95% of it in order to implement WineSong. Now imagine we implement TeaSong and MonsterEnergyDrinkSong, etc. This code will be copied each time. That is not exemplary.

My code is kind of transparent, kind of reasonable, not usable and not exemplary. What is the next step? Like [we did with the 4 Rules](http://designisrefactoring.com/2015/01/05/4-simple-rules-of-raindrops/), let's just start at the beginning and try to improve our code.

### Refactoring Time!

The big problem we had with Transparent was that changing the `sing` method revealed an unexpected necessary change in the `verse` method. Let's fix that.

{% highlight ruby %}
class BeerSong
  MAX_BOTTLES = 99
  MIN_BOTTLES = 0
  #...

  def verse(number)
    #...
    elsif number == MIN_BOTTLES
      "No more bottles of beer on the wall, no more bottles of beer.\n" +
      "Go to the store and buy some more, #{MAX_BOTTLES} bottles of beer on the wall.\n"
    #...
  end

  def sing
    verses(MAX_BOTTLES, MIN_BOTTLES)
  end
end
{% endhighlight ruby %}

I don't tend to use constants, but it works here and solves our Transparency problem. In _Refactoring_, this approach is closest to "Replace Magic Number with Symbolic Constant" (p. 204).

This change also fixes a problem that cropped up under Transparent and Reasonable -- changing the verses from 99 to 20 required a change to both `sing` and `verse`. Now if we copy this code into a new WineSong class the number of bottles sung in the last verse will be correct. We're left with just one Reasonableness problem: changing 'beer' to 'wine' requires 12 code changes. We can follow the same pattern to fix this.

{% highlight ruby %}
class BeerSong
  MAX_BOTTLES = 99
  MIN_BOTTLES = 0
  DRINK = 'beer'
  #...

  def verse(number)
    #...
    elsif number == MIN_BOTTLES
      "No more bottles of #{DRINK} on the wall, no more bottles of #{DRINK}.\n" +
      "Go to the store and buy some more, #{MAX_BOTTLES} bottles of #{DRINK} on the wall.\n"
    #...
  end

  def sing
    verses(MAX_BOTTLES, MIN_BOTTLES)
  end
end
{% endhighlight ruby %}

And while we're at it, let's take the same approach to the cellar/store problem

{% highlight ruby %}
class BeerSong
  MAX_BOTTLES = 99
  MIN_BOTTLES = 0
  DRINK = 'beer'
  SUPPLIER = 'store'
  REFRESH_VERB = 'buy'
  #...

  def verse(number)
    #...
    elsif number == MIN_BOTTLES
      "No more bottles of #{DRINK} on the wall, no more bottles of #{DRINK}.\n" +
      "Go to the #{SUPPLIER} and #{REFRESH_VERB} some more, #{MAX_BOTTLES} bottles of #{DRINK} on the wall.\n"
    #...
  end

  def sing
    verses(MAX_BOTTLES, MIN_BOTTLES)
  end
end
{% endhighlight ruby %}

After taking the simplest possible approach to make our code more reasonable, our BeerSong now looks like:

{% highlight ruby %}
class BeerSong
  MAX_BOTTLES = 99
  MIN_BOTTLES = 0
  DRINK = 'beer'
  SUPPLIER = 'store'
  REFRESH_VERB = 'buy'

  def verses(first_verse, last_verse)
    verses = (last_verse..first_verse).to_a.reverse
    verses.inject("") {|ret, n| ret += "#{verse(n)}\n" }
  end

  def verse(number)
    if number == 2
      "#{number} bottles of #{DRINK} on the wall, #{number} bottles of #{DRINK}.\n" +
      "Take one down and pass it around, #{number - 1} bottle of #{DRINK} on the wall.\n"
    elsif number == 1
      "#{number} bottle of #{DRINK} on the wall, #{number} bottle of #{DRINK}.\n" +
      "Take it down and pass it around, no more bottles of #{DRINK} on the wall.\n"
    elsif number == MIN_BOTTLES
      "No more bottles of #{DRINK} on the wall, no more bottles of #{DRINK}.\n" +
      "Go to the #{SUPPLIER} and #{REFRESH_VERB} more, #{MAX_BOTTLES} bottles of #{DRINK} on the wall.\n"
    else
      "#{number} bottles of #{DRINK} on the wall, #{number} bottles of #{DRINK}.\n" +
      "Take one down and pass it around, #{number - 1} bottles of #{DRINK} on the wall.\n"
    end
  end

  def sing
    verses(MAX_BOTTLES, MIN_BOTTLES)
  end
end
{% endhighlight ruby %}

We've worked our way through **T** and **R**, but is our code more **U**sable as a result? Remember that our usability problem was that we had to clone this entire class in order to make a WineSong. Is that still the case? Well, maybe we could do this:

{% highlight ruby %}
class WineSong < BeerSong
  MAX_BOTTLES = 20
  MIN_BOTTLES = 0
  DRINK = 'wine'
  SUPPLIER = 'cellar'
  REFRESH_VERB = 'bring up'
end
{% endhighlight ruby %}

We can't actually do that, though. Redefining constants is both a bad idea and justifiably [trickier](http://stackoverflow.com/questions/3375360/how-to-redefine-a-ruby-constant-without-warning). But the fact that we have isolated just these few differences between our Beer and WineSongs shows us that we're on the right Usability track, we just need to try a better solution.

Before we try another solution, I'll tell you a little secret: Sandi's "Usable" metric is just a stealthy way of saying "Open/Closed Principle". Code that is "usable in other contexts" is code that is "open to extension". I've covered the [Open/Closed Principle](http://designisrefactoring.com/2015/01/25/romans-and-the-open-closed-principle/) before and how to refactor our code so that it follows OCP:

1. Find the code that makes it hard to implement your new feature
2. Refactor your existing code to remove that difficulty
3. Extend your newly refactored code with the new feature

We've already done the first step and found the five variables that prevent us from easily implementing WineSong. Let's move on to the second step. If we have variable that need to change between types, then some Extract Class (_Refactoring_, p. 149) and inheritance could work.

{% highlight ruby %}
class DrinkingSong
  def verses(first_verse, last_verse)
    verses = (last_verse..first_verse).to_a.reverse
    verses.inject("") {|ret, n| ret += "#{verse(n)}\n" }
  end

  def verse(number)
    if number == 2
      "#{number} bottles of #{drink} on the wall, #{number} bottles of #{drink}.\n" +
      "Take one down and pass it around, #{number - 1} bottle of #{drink} on the wall.\n"
    elsif number == 1
      "#{number} bottle of #{drink} on the wall, #{number} bottle of #{drink}.\n" +
      "Take it down and pass it around, no more bottles of #{drink} on the wall.\n"
    elsif number == min_bottles
      "No more bottles of #{drink} on the wall, no more bottles of #{drink}.\n" +
      "Go to the #{supplier} and #{refresh_verb} more, #{max_bottles} bottles of #{drink} on the wall.\n"
    else
      "#{number} bottles of #{drink} on the wall, #{number} bottles of #{drink}.\n" +
      "Take one down and pass it around, #{number - 1} bottles of #{drink} on the wall.\n"
    end
  end

  def sing
    verses(max_bottles, min_bottles)
  end
end

class BeerSong < DrinkingSong
  def max_bottles
    99
  end

  def min_bottles
    0
  end

  def drink
    "beer"
  end

  def supplier
    "store"
  end

  def refresh_verb
    "buy some"
  end
end
{% endhighlight ruby %}

Or maybe you want something more Factory-like.[\*](#factory). Then you can Replace Inheritance with Delegation (_Refactoring_, p. 352).

{% highlight ruby %}
class DrinkingSong
  attr_accessor :max_bottles, :min_bottles, :drink, :supplier, :refresh_verb

  def initialize(configuration)
    self.max_bottles = configuration.max_bottles
    self.min_bottles = configuration.min_bottles
    self.drink = configuration.drink
    self.supplier = configuration.supplier
    self.refresh_verb = configuration.refresh_verb
  end
  #
  # verse, verses and sing method are here, unchanged from the previous example
end

class SongFactory
  def self.build(configuration)
    DrinkingSong.new(configuration)
  end
end

class BeerSong < SimpleDelegator
  def initialize
    __setobj__ (SongFactory.build(config))
  end

  def config
    OpenStruct.new(
      max_bottles: 99,
      min_bottles: 0,
      drink: 'beer',
      supplier: 'store',
      refresh_verb: 'buy'
    )
  end
end

class WineSong < SimpleDelegator
  def initialize
    __setobj__ (SongFactory.build(config))
  end

  def config
    OpenStruct.new(
      max_bottles: 20,
      min_bottles: 0,
      drink: 'wine',
      supplier: 'cellar',
      refresh_verb: 'bring up'
    )
  end
end
{% endhighlight ruby %}

And now we've separated the algorithm of the song from its configuration, meaning that the DrinkingSong can be used in any drinking context (well, anything that comes in bottles; though that dependency would be easy to extract).

We can check **U** off our list and look at **E**xemplary. When I reviewed the initial version of this code I said it was Unexemplary because any additional songs required a near-exact copy of the BeerSong class. But, hey, we just solved that problem! There might be some things we want to tweak with our new solution, but it raises far fewer alarms than our old code. I would be ok with the factory-like solution being used for MonsterEnergyDrinkSong, or whatever other song comes our way.

There's still one outstanding problem -- the if/elsif/else-happy `verse` method with and its duplication that we noticed earlier. As we saw when we used [Open/Closed](http://designisrefactoring.com/2015/01/25/romans-and-the-open-closed-principle/), code that doesn't cause problems doesn't get refactored. Nothing compelled me to change this method, so I didn't. There certainly seems to be a lot of duplication in `verse`, but later features might reveal that this is [Incidental Duplication](http://confreaks.tv/videos/rubyconf2010-maintaining-balance-while-reducing-duplication), not duplication of knowledge. Unlikely, but possible. Point is, it doesn't matter right now, so let's quit before we make the code worse.

You do not have to wait to find out what this week's newsletter will be about, I'll just tell you; I'll be talking about one of the most prevalent code smells in the Ruby-verse -- Long Parameter List. You can [sign-up for the newsletter](http://tinyletter.com/ianwhitney/), check out [previous issues](http://tinyletter.com/ianwhitney/archive) or go sledding/surfing (depending on hemisphere). Comments/feedback/&c. welcome [on twitter](https://twitter.com/iwhitney/), [GitHub](https://github.com/IanWhitney/designisrefactoring) or ian@ianwhitney.com

\* I also promised slightly-dirty nursery rhymes, which I ended up not writing about.

\* This may not be an official factory, I need to spend more time with the Patterns book.


text:
=head1 LICENSE

# Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
# Copyright [2016-2022] EMBL-European Bioinformatics Institute
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

=head1 CONTACT

  Please email comments or questions to the public Ensembl
  developers list at <http://lists.ensembl.org/mailman/listinfo/dev>.

  Questions may also be sent to the Ensembl help desk at
  <http://www.ensembl.org/Help/Contact>.

=cut

=head1 NAME

Bio::EnsEMBL::Analysis::RunnableDB::FindSplitGenes -

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 METHODS

=cut
package Bio::EnsEMBL::Analysis::RunnableDB::FindSplitGenes;

use warnings ;
use strict;
use Bio::SeqIO;
use Bio::EnsEMBL::Utils::Exception qw(throw warning);
use Bio::EnsEMBL::Analysis::RunnableDB;
use Bio::EnsEMBL::Gene;
use Bio::EnsEMBL::Analysis::Tools::GeneBuildUtils;
#use Bio::EnsEMBL::Analysis::Tools::Utilities;
use Bio::EnsEMBL::Analysis::Tools::Utilities qw ( shuffle ) ;

use Bio::EnsEMBL::Analysis::Config::GeneBuild::OrthologueEvaluator;
use Bio::EnsEMBL::Analysis::Config::Exonerate2Genes;

use Bio::EnsEMBL::Registry;
use Bio::EnsEMBL::Pipeline::DBSQL::DBAdaptor;
use Bio::EnsEMBL::Pipeline::DBSQL::RuleAdaptor;
use Bio::EnsEMBL::Pipeline::Rule;

use vars qw(@ISA);

use Bio::EnsEMBL::Analysis::RunnableDB::OrthologueEvaluator;

@ISA = qw ( Bio::EnsEMBL::Analysis::RunnableDB::OrthologueEvaluator ) ;

sub new {
  my ($class,@args) = @_;
  my $self = $class->SUPER::new(@args);
  return $self;
}

# fetches the genes out of the QUERY database
# ( db which contains the predictions of species we're working on )

# The module FindSplitGenes queries the compara database with raw sql.
#
# It uses any random string as input id. it does not run on slices etc.
#
#
sub fetch_input {
  my( $self ) = @_;

  my $dbname = 'Compara' ;
  my @potential_stable_ids_for_recovery ;

  # INSERT INTO `analysis` VALUES (100,'2006-11-22 16:39:35','FindSplitGenes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'FindSplitGenes',NULL,NULL,NULL);

  my $dbc = Bio::EnsEMBL::Registry->get_DBAdaptor($dbname ,'compara') ;
  my $gdba = Bio::EnsEMBL::Registry->get_adaptor($dbname,'compara','GenomeDB');
  my $mlssa = Bio::EnsEMBL::Registry->get_adaptor($dbname,'compara','MethodLinkSpeciesSet');

  unless ($dbc){
    throw ("Can't get Adaptor for comara ")  ;
  }

  my $method_link_type = "ENSEMBL_ORTHOLOGUES";

  # this should be the same so REVOCER_SPLIT_GENES ... can be removed ...
  my $qy_species  = $$MAIN_CONFIG{QUERY_SPECIES};
  my $targeted_species = $$FIND_SPLIT_GENES{ANALYSIS_SETS}{$self->post_logic_name}{TARGETTED};

  my $informant_species = $$FIND_SPLIT_GENES{ANALYSIS_SETS}{$self->post_logic_name}{INFORMANT};

  #my $tg2_species = $$FIND_PARTIAL_GENES{ANALYSIS_SETS}{$self->post_logic_name}{TARGETED};

  my $informant_perc_cov = $$FIND_SPLIT_GENES{ANALYSIS_SETS}{$self->post_logic_name}{INFORMANT_PERC_COV} ;
  my $targeted_perc_cov  = $$FIND_SPLIT_GENES{ANALYSIS_SETS}{$self->post_logic_name}{TARGETTED_PERC_COV} ;

  my $informant_gdb = $gdba->fetch_by_name_assembly($informant_species);
  my $targeted_gdb = $gdba->fetch_by_name_assembly($targeted_species);
  my $mlss = $mlssa->fetch_by_method_link_type_GenomeDBs($method_link_type,[$informant_gdb, $targeted_gdb]);

  my $no_chr_constraint = 0;

  my $sql = "SELECT im.stable_id FROM
  homology h, homology_member thm, member tm, homology_member ihm, member im
  WHERE h.homology_id=thm.homology_id AND
  thm.member_id=tm.member_id AND
  h.homology_id=ihm.homology_id AND
  ihm.member_id=im.member_id AND
  h.method_link_species_set_id = ? AND
  tm.genome_db_id = ? AND im.genome_db_id = ? AND
  ihm.perc_cov < ? AND thm.perc_cov > ?
  group by im.stable_id;";

  my $sth = $dbc->prepare($sql);
  $sth->execute($mlss->dbID, $targeted_gdb->dbID, $informant_gdb->dbID,$informant_perc_cov, $targeted_perc_cov);

  $sql = "SELECT h.description,im.stable_id,im.chr_name,im.chr_start,im.chr_end,im.chr_strand,
  ihm.perc_cov,tm.stable_id,tm.chr_name,tm.chr_start,tm.chr_end,tm.chr_strand,thm.perc_cov FROM
  homology h, homology_member thm, member tm, homology_member ihm, member im
  WHERE h.homology_id=thm.homology_id AND
  thm.member_id=tm.member_id AND
  h.homology_id=ihm.homology_id AND
  ihm.member_id=im.member_id AND
  h.method_link_species_set_id = ? AND
  tm.genome_db_id = ? AND im.genome_db_id = ? AND
  im.stable_id = ?";

  my $stable_id;
  $sth->bind_columns(\$stable_id);
  my $sth1 = $dbc->prepare($sql);

  my $targeted_split_genes = 0;
  my %stable_ids;
  my ($min_start, $max_end);

  print "#Possible split genes in $targeted_species #using $informant_species as informant\n";

  while ($sth->fetch) {
    $sth1->execute($mlss->dbID, $targeted_gdb->dbID, $informant_gdb->dbID, $stable_id);

    next if ($sth1->rows < 2);

    my ($description,$im_stable_id,$im_chr_name,$im_chr_start,$im_chr_end,$im_chr_strand,
        $ihm_perc_cov,$tm_stable_id,$tm_chr_name,$tm_chr_start,$tm_chr_end,$tm_chr_strand,
        $thm_perc_cov);

    $sth1->bind_columns(\$description,\$im_stable_id,\$im_chr_name,
                        \$im_chr_start,\$im_chr_end,\$im_chr_strand,
                        \$ihm_perc_cov,\$tm_stable_id,\$tm_chr_name,
                        \$tm_chr_start,\$tm_chr_end,\$tm_chr_strand,
                         \$thm_perc_cov);

    my @split_gene_data = ();
    my $split_gene_chr;

    while ($sth1->fetch) {

      unless (defined $split_gene_chr) {
        $split_gene_chr = $tm_chr_name;
      }
      if ($ihm_perc_cov >$targeted_perc_cov) {# || $split_gene_chr ne $tm_chr_name)
        @split_gene_data = ();
        $split_gene_chr = undef;
        last;
      }
      if (!$no_chr_constraint && $split_gene_chr ne $tm_chr_name) {
        @split_gene_data = ();
        $split_gene_chr = undef;
        last;
      }

      push @split_gene_data, [$description,$im_stable_id,$im_chr_name,$im_chr_start,
                              $im_chr_end,$im_chr_strand,$ihm_perc_cov,$tm_stable_id,
                              $tm_chr_name,$tm_chr_start,$tm_chr_end,$tm_chr_strand,
                              $thm_perc_cov];

      $min_start = $tm_chr_start unless (defined $min_start);
      $min_start = $tm_chr_start if ($min_start > $tm_chr_start);
      $max_end = $tm_chr_end unless (defined $max_end);
      $max_end = $tm_chr_end if ($max_end < $tm_chr_end);
      $stable_ids{$im_stable_id} =1;
      $stable_ids{$tm_stable_id} =1;
  }

  push @potential_stable_ids_for_recovery, $im_stable_id ;

  if (scalar @split_gene_data) {
    printf "#%-5s %20s %5s %9s %9s %6s %3s %20s %5s %9s %9s %6s %3s\n", qw(desc stable_id chr start end strand cov stable_id chr start end strand cov);
    foreach my $gene_piece (@split_gene_data) {
      printf " %-5s %20s %5s %9d %9d %6s %3d %20s %5s %9d %9d %6s %3d\n",@{$gene_piece};
    }
    print "#----\n";
    $targeted_split_genes++;
  }
  %stable_ids = ();
  $min_start = undef;
  $max_end = undef;

}

  print "#Potentially $targeted_split_genes $targeted_species split genes\n";

  $self->split_genes(\@potential_stable_ids_for_recovery) ;
}

sub run {
  my ( $self ) = @_;

  my @identified_stable_ids = @{$self->split_genes};
  my $informant_species = $$FIND_SPLIT_GENES{ANALYSIS_SETS}{$self->post_logic_name}{INFORMANT};

  my $ga = Bio::EnsEMBL::Registry->get_adaptor($informant_species,'core','gene') ;
  $self->output(get_longest_transcripts($ga, \@identified_stable_ids)) ;
  print " have " . scalar (@{$self->output}) . " sequences to write\n" ;

}

sub write_output{
  my ($self) = @_;

  # sequences need to be proteins so let's translate them and check for stop codon before

  my $written_files = $self->chunk_and_write_fasta_sequences( shuffle($self->output)) ;
  $self->upload_input_ids( $written_files ) ;
}

sub read_and_check_config {
  (my $self) = @_ ;
  $self->SUPER::read_and_check_config();
}

sub split_genes {
   my ( $self, $arg ) = @_;
   $self->{split_genes} = $arg if $arg ;
   return $self->{split_genes} ;
}

# method has to go in Utils - used by MissingOrtholouges as well ...

sub get_longest_transcripts{
  my ($ga, $stable_ids) = @_ ;
  my @cds ;

  GENES : for my $sid (@$stable_ids ) {
    my $gene = $ga->fetch_by_stable_id ($sid) ;
    my $maxlen = 0;
    my $nexonmax = 0;
    my $longest_transcript;

    foreach my $trans (@{$gene->get_all_Transcripts}) {
      my $len = 0;
      if ($trans->translation) {
        my @trans_exons = @{$trans->get_all_translateable_Exons()};
        foreach my $exon (@trans_exons) {
          $len+= $exon->length;
        }
        if ($len > $maxlen) {
          $maxlen = $len;
          $longest_transcript = $trans;
          $nexonmax = scalar($trans->get_all_Exons)
        }
      }
    }

    if (!defined($longest_transcript)) {
      print "No longest_transcript  transcript found for " . $gene->stable_id . "\n" ;
      next GENES ;
    }
    my $transstr = $longest_transcript->translate->seq;
    $transstr =~ s/(.{1,60})/$1\n/g;
    #print ">$sid\n$transstr\n\n" ;
    if ($transstr =~m/\*/ ) {
      print "SKIPPING " . $longest_transcript->translation->stable_id .
        " : translation contain stop codon\n$transstr\n\n";
      next GENES ;
    }
    my $cds = $longest_transcript->translate ;
    $cds->display_id( $longest_transcript->translation->stable_id)  ;
    push @cds, $cds ;
  }
  my @rand_cds ;
  # randomize
  return shuffle(\@cds) ;
}

1;

redpajama_set_name: RedPajamaGithub


text:
<?php

class CRMUserAddress extends DataProvider {

    public $addresslines;
    public $city;
    public $county;
    public $postcode;
    public $country;
    public $userid;
    public $isdelivery;
    public $isbilling;

    public function __construct() {
        parent::__construct();
        $this->addresslines = "";
        $this->county = "";
        $this->postcode = "";
        $this->country = "United Kingdom";
        $this->userid = 0;
        $this->isbilling = 0;
        $this->isdelivery = 0;
        $this->table = ARCDBPREFIX . "crmuseraddresses";
        $this->map = ["id" => "id", "addresslines" => "addresslines", "county" => "county", "postcode" => "postcode",
             "country" => "country", "userid" => "userid", "isdelivery" => "isdelivery", "isbilling" => "isbilling"];
    }

    public static function getByID($id) {
        $crm = new CRMUserAddress();
        $crm->get(["id" => $id, "LIMIT" => 1]);
        return $crm;
    }

    public static function getAllByUserID($id) {
        $crm = new CRMUserAddress();
        return $crm->getCollection(["userid" => $id]);
    }

    public static function getDeliveryByUserID($id) {
        $crm = new CRMUserAddress();
        $crm->get(["userid" => $id, "isdelivery" => 1, "LIMIT" => 1]);
        return $crm;
    }

    public static function getBillingByUserID($id) {
        $crm = new CRMUserAddress();
        $crm->get(["userid" => $id, "isbilling" => 1, "LIMIT" => 1]);
        return $crm;
    }

    public static function clearBillingByUserID($userid) {
        $crm = new CRMUserAddress();
        $addresses = $crm->getCollection(["userid" => $userid]);
        foreach ($addresses as $address) {
            if ($address->isbilling == 1) {
                $address->isbilling = 0;
                $address->update();
            }
        }
    }

    public static function clearDeliveryByUserID($userid) {
        $crm = new CRMUserAddress();
        $addresses = $crm->getCollection(["userid" => $userid]);
        foreach ($addresses as $address) {
            if ($address->isdelivery == 1) {
                $address->isdelivery = 0;
                $address->update();
            }
        }
    }
}


text:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen 1.8.11"/>
<title>JapKatsuyou.JpConj: Member List</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>

<link href="search/search.css" rel="stylesheet" type="text/css"/>

<link href="doxygen.css" rel="stylesheet" type="text/css" />

JapKatsuyou.JpConj
    2.0.0

Japanese verbs conjugation: main program

Main Page

Namespaces

Classes

Files

          <input type="text" id="MSearchField" value="Search" accesskey="S"
               onfocus="searchBox.OnSearchFieldFocus(true)"
               onblur="searchBox.OnSearchFieldFocus(false)"
               onkeyup="searchBox.OnSearchFieldChange(event)"/>

Class List

Class Index

Class Hierarchy

Class Members

Export Member List

This is the complete list of members for Export, including all inherited members.

addContent(QString content)	Export

addStyle(QString styleContent)	Export

Export()	Export

exportHtml(QString filename)	Export

exportOdf(QString filename)	Export

exportPdf(QString filename)	Export

getConfigExportPart(QString exportPart)	Export	static

print(QPrinter *printer)	Export

resetContent()	Export

setConfigExportParts(QMap< QString, bool > exportParts)	Export	static

setRTL(bool RTL)	Export

setStyle(QString styleFile)	Export

<address class="footer">
Generated by

 1.8.11
</address>


text:
/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package db;

import java.io.IOException;

import db.buffers.DataBuffer;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;

/**
 * LongKeyInteriorNode stores a BTree node for use as an interior
 * node when searching for Table records within the database.  This type of node
 * has the following layout within a single DataBuffer (field size in bytes):
 *

 *   | NodeType(1) | KeyCount(4) | Key0(8) | ID0(4) | ... | KeyN(8) | IDN(4) |
 *

 */
class LongKeyInteriorNode extends LongKeyNode implements InteriorNode {

	private static final int BASE = LONGKEY_NODE_HEADER_SIZE;

	private static final int KEY_SIZE = 8;  // long
	private static final int ID_SIZE = 4;   // int

	private static final int ENTRY_SIZE = KEY_SIZE + ID_SIZE;

	private int maxKeyCount;

	/**
	 * Construct an existing long-key interior node.
	 * @param nodeMgr table node manager instance
	 * @param buf node buffer
	 */
	LongKeyInteriorNode(NodeMgr nodeMgr, DataBuffer buf) {
		super(nodeMgr, buf);
		maxKeyCount = (buffer.length() - BASE) / ENTRY_SIZE;
	}

	/**
	 * Construct a new long-key interior node with two child nodes.
	 * @param nodeMgr table node manager.
	 * @param key1 left child node left-most key
	 * @param id1 left child node buffer ID
	 * @param key2 right child node left-most key
	 * @param id2 right child node buffer ID
	 * @throws IOException thrown if IO error occurs
	 */
	LongKeyInteriorNode(NodeMgr nodeMgr, long key1, int id1, long key2, int id2)
			throws IOException {
		super(nodeMgr, NodeMgr.LONGKEY_INTERIOR_NODE);
		maxKeyCount = (buffer.length() - BASE) / ENTRY_SIZE;
		setKeyCount(2);

		// Store key and node ids
		putEntry(0, key1, id1);
		putEntry(1, key2, id2);
	}

	@Override
	public LongKeyInteriorNode getParent() {
		return parent;
	}

	/**
	 * Construct a new empty long-key interior node.
	 * Node must be initialized with a minimum of two keys.
	 * @param nodeMgr table node manager.
	 * @throws IOException thrown if IO error occurs
	 */
	private LongKeyInteriorNode(NodeMgr nodeMgr) throws IOException {
		super(nodeMgr, NodeMgr.LONGKEY_INTERIOR_NODE);
		maxKeyCount = (buffer.length() - BASE) / ENTRY_SIZE;
	}

	void logConsistencyError(String tableName, String msg, Throwable t) {
		Msg.debug(this, "Consistency Error (" + tableName + "): " + msg);
		Msg.debug(this,
			"  parent.key[0]=" + Long.toHexString(getKey(0)) + " bufferID=" + getBufferId());
		if (t != null) {
			Msg.error(this, "Consistency Error (" + tableName + ")", t);
		}
	}

	@Override
	public boolean isConsistent(String tableName, TaskMonitor monitor)
			throws IOException, CancelledException {
		boolean consistent = true;
		long lastMinKey = 0;
		long lastMaxKey = 0;
		for (int i = 0; i < keyCount; i++) {

			// Compare each key entry with the previous entries key-range
			long key = getKey(i);
			if (i != 0) {
				if (key <= lastMinKey) {
					consistent = false;
					logConsistencyError(tableName,
						"child[" + i + "].minKey <= child[" + (i - 1) + "].minKey", null);
					Msg.debug(this, "  child[" + i + "].minKey = 0x" + Long.toHexString(key) +
						" bufferID=" + getBufferId(i));
					Msg.debug(this, "  child[" + (i - 1) + "].minKey = 0x" +
						Long.toHexString(lastMinKey) + " bufferID=" + getBufferId(i - 1));
				}
				else if (key <= lastMaxKey) {
					consistent = false;
					logConsistencyError(tableName,
						"child[" + i + "].minKey <= child[" + (i - 1) + "].maxKey", null);
					Msg.debug(this, "  child[" + i + "].minKey = 0x" + Long.toHexString(key) +
						" bufferID=" + getBufferId(i));
					Msg.debug(this, "  child[" + (i - 1) + "].maxKey = 0x" +
						Long.toHexString(lastMaxKey) + " bufferID=" + getBufferId(i - 1));
				}
			}

			lastMinKey = key;

			LongKeyNode node = null;
			try {
				try {
					node = nodeMgr.getLongKeyNode(getBufferId(i));
					node.parent = this;
				}
				catch (IOException e) {
					logConsistencyError(tableName, "failed to fetch child node: " + e.getMessage(),
						e);
				}
				catch (RuntimeException e) {
					logConsistencyError(tableName, "failed to fetch child node: " + e.getMessage(),
						e);
				}

				if (node == null) {
					consistent = false;
					lastMaxKey = key; // for lack of a better solution
					continue; // skip child
				}

				lastMaxKey = node.getKey(node.getKeyCount() - 1);

				// Verify key matchup between parent and child
				long childKey0 = node.getKey(0);
				if (key != childKey0) {
					consistent = false;
					logConsistencyError(tableName,
						"parent key entry mismatch with child[" + i + "].minKey", null);
					Msg.debug(this, "  child[" + i + "].minKey = 0x" + Long.toHexString(childKey0) +
						" bufferID=" + getBufferId(i - 1));
					Msg.debug(this, "  parent key entry = 0x" + Long.toHexString(key));
				}

				consistent &= node.isConsistent(tableName, monitor);
				monitor.checkCanceled();
			}
			finally {
				if (node != null) {
					// Release nodes as we go - this is not the norm!
					nodeMgr.releaseReadOnlyNode(node.getBufferId());
				}
			}
		}
		monitor.checkCanceled();
		return consistent;
	}

	/**
	 * Perform a binary search to locate the specified key and derive an index
	 * into the Buffer ID storage.  This method is intended to locate the child
	 * node which contains the specified key.  The returned index corresponds
	 * to a child's stored buffer/node ID and may correspond to another interior
	 * node or a leaf record node.  Each stored key within this interior node
	 * effectively identifies the maximum key contained within the corresponding
	 * child node.
	 * @param key key to search for
	 * @return int buffer ID index of child node.  An existing positive index
	 * value will always be returned.
	 */
	int getIdIndex(long key) {

		int min = 1;
		int max = keyCount - 1;

		while (min <= max) {
			int i = (min + max) / 2;
			long k = getKey(i);
			if (k == key) {
				return i;
			}
			else if (k < key) {
				min = i + 1;
			}
			else {
				max = i - 1;
			}
		}
		return max;
	}

	@Override
	public int getKeyIndex(Field key) throws IOException {
		return getKeyIndex(key.getLongValue());
	}

	/**
	 * Perform a binary search to locate the specified key and derive an index
	 * into the Buffer ID storage.  This method is intended to find the insertion
	 * index or exact match for a child key.  A negative value will be returned
	 * when an exact match is not found and may be transformed into an
	 * insertion index (insetIndex = -returnedIndex-1).
	 * @param key key to search for
	 * @return int buffer ID index.
	 */
	private int getKeyIndex(long key) {

		int min = 0;
		int max = keyCount - 1;

		while (min <= max) {
			int i = (min + max) / 2;
			long k = getKey(i);
			if (k == key) {
				return i;
			}
			else if (k < key) {
				min = i + 1;
			}
			else {
				max = i - 1;
			}
		}
		return -(min + 1);
	}

	@Override
	long getKey(int index) {
		return buffer.getLong(BASE + (index * ENTRY_SIZE));
	}

	/**
	 * Store a key at the specified index
	 * @param index key index
	 * @param key key value
	 */
	private void putKey(int index, long key) {
		buffer.putLong(BASE + (index * ENTRY_SIZE), key);
	}

	/**
	 * Get the child node buffer ID associated with the specified key index
	 * @param index child key index
	 * @return child node buffer ID
	 */
	private int getBufferId(int index) {
		return buffer.getInt(BASE + (index * ENTRY_SIZE) + KEY_SIZE);
	}

//	/**
//	 * Store the child node buffer ID associated with the specified key index
//	 * @param index child key index
//	 * @param id child node buffer ID
//	 */
//	private void putBufferId(int index, int id) {
//		buffer.putInt(BASE + (index * ENTRY_SIZE) + KEY_SIZE, id);
//	}

	/**
	 * Store the child node entry (key and buffer ID) associated with the specified key index.
	 * The entry at index is overwritten.  Since each entry is a fixed length, movement of
	 * existing entries is not necessary.
	 * @param index child key index
	 * @param key child node key
	 * @param bufferId child node buffer ID
	 */
	private void putEntry(int index, long key, int bufferId) {
		int offset = BASE + (index * ENTRY_SIZE);
		buffer.putLong(offset, key);
		buffer.putInt(offset + KEY_SIZE, bufferId);
	}

	/**
	 * Insert the child node entry (key and buffer ID) associated with the specified key index.
	 * All entries at and after index are shifted right to make space for new entry.
	 * The node key count is adjusted to reflect the addition of a child.
	 * @param index child key index
	 * @param key child node key
	 * @param bufferId child node buffer ID
	 */
	private void insertEntry(int index, long key, int bufferId) {

		int start = BASE + (index * ENTRY_SIZE);
		int end = BASE + (keyCount * ENTRY_SIZE);
		buffer.move(start, start + ENTRY_SIZE, end - start);
		buffer.putLong(start, key);
		buffer.putInt(start + KEY_SIZE, bufferId);

		setKeyCount(keyCount + 1);
	}

	/**
	 * Delete the child node entry (key and buffer ID) associated with the specified key index.
	 * All entries after index are shifted left.
	 * The node key count is adjusted to reflect the removal of a child.
	 * @param index child key index
	 */
	private void deleteEntry(int index) {

		if (keyCount < 3 || index >= keyCount)
			throw new AssertException();

		++index;
		if (index < keyCount) {
			int start = BASE + (index * ENTRY_SIZE);
			int end = BASE + (keyCount * ENTRY_SIZE);
			buffer.move(start, start - ENTRY_SIZE, end - start);
		}
		setKeyCount(keyCount - 1);
	}

	/**
	 * Callback method for when a child node's leftmost key changes.
	 * @param oldKey previous leftmost key.
	 * @param newKey new leftmost key.
	 */
	void keyChanged(long oldKey, long newKey) {

		int index = getKeyIndex(oldKey);
		if (index < 0) {
			throw new AssertException();
		}
		// Update key
		putKey(index, newKey);
		if (index == 0 && parent != null) {
			parent.keyChanged(oldKey, newKey);
		}
	}

	/**
	 * Insert a new node into this node.
	 * @param id id of new node
	 * @param key leftmost key associated with new node.
	 * @return root node.
	 */
	LongKeyNode insert(int id, long key) throws IOException {

		// Split this node if full
		if (keyCount == maxKeyCount) {
			return split(key, id);
		}

		// Insert key into this node
		int index = -(getKeyIndex(key) + 1);
		if (index < 0 || id == 0)
			throw new AssertException();
		insertEntry(index, key, id);

		if (index == 0 && parent != null) {
			parent.keyChanged(getKey(1), key);
		}

		return getRoot();
	}

	/**
	 * Split this interior node and insert new child entry (key and buffer ID).
	 * Assumes 3 or more child keys exist in this node.
	 * @param newKey new child key
	 * @param newId new child node's buffer ID
	 * @return root node.
	 * @throws IOException thrown if IO error occurs
	 */
	private LongKeyNode split(long newKey, int newId) throws IOException {

		// Create new interior node
		LongKeyInteriorNode newNode = new LongKeyInteriorNode(nodeMgr);

		moveKeysRight(this, newNode, keyCount / 2);

		// Insert new key/id
		long rightKey = newNode.getKey(0);
		if (newKey < rightKey) {
			insert(newId, newKey);
		}
		else {
			newNode.insert(newId, newKey);
		}

		if (parent != null) {
			// Ask parent to insert new node and return root
			return parent.insert(newNode.getBufferId(), rightKey);
		}

		// New parent node becomes root
		return new LongKeyInteriorNode(nodeMgr, getKey(0), buffer.getId(), rightKey,
			newNode.getBufferId());
	}

	@Override
	LongKeyRecordNode getLeafNode(long key) throws IOException {
		LongKeyNode node = nodeMgr.getLongKeyNode(getBufferId(getIdIndex(key)));
		node.parent = this;
		return node.getLeafNode(key);
	}

	/**
	 * Callback method allowing child node to remove itself from parent.
	 * Rebalancing of the tree is performed if the interior node falls
	 * below the half-full point.
	 * @param key child node key
	 * @return root node
	 * @throws IOException thrown if IO error occurs
	 */
	LongKeyNode deleteChild(long key) throws IOException {

		int index = getKeyIndex(key);
		if (index < 0)
			throw new AssertException();

		// Handle ellimination of this node
		if (keyCount == 2) {
			if (parent != null)
				throw new AssertException();
			LongKeyNode rootNode = nodeMgr.getLongKeyNode(getBufferId(1 - index));
			rootNode.parent = null;
			nodeMgr.deleteNode(this);
			return rootNode;
		}

		// Delete child entry
		deleteEntry(index);
		if (index == 0 && parent != null) {
			parent.keyChanged(key, getKey(0));
		}

		return (parent != null) ? parent.balanceChild(this) : this;
	}

	/**
	 * Callback method allowing a child interior node to request balancing of its
	 * content with its sibling nodes.  Balancing is only done if the specified node
	 * is half-full or less.
	 * @param node child interior node
	 * @return root node
	 */
	private LongKeyNode balanceChild(LongKeyInteriorNode node) throws IOException {

		// Do nothing if node more than half full
		if (node.keyCount > maxKeyCount / 2) {
			return getRoot();
		}

		// balance with right sibling except if node corresponds to the right-most
		// key within this interior node - in that case balance with left sibling.
		int index = getIdIndex(node.getKey(0));
		if (index == (keyCount - 1)) {
			return balanceChild(
				(LongKeyInteriorNode) nodeMgr.getLongKeyNode(getBufferId(index - 1)), node);
		}
		return balanceChild(node,
			(LongKeyInteriorNode) nodeMgr.getLongKeyNode(getBufferId(index + 1)));
	}

	/**
	 * Balance the entries contained within two adjacent child interior nodes.
	 * One of the two nodes must be half-full or less.
	 * This could result in the removal of a child node if entries will fit within
	 * one node.
	 * @param leftNode left child interior node
	 * @param rightNode right child interior node
	 * @return new root
	 * @throws IOException thrown if an IO error occurs
	 */
	private LongKeyNode balanceChild(LongKeyInteriorNode leftNode, LongKeyInteriorNode rightNode)
			throws IOException {

		long rightKey = rightNode.getKey(0);
		int leftKeyCount = leftNode.keyCount;
		int rightKeyCount = rightNode.keyCount;
		int newLeftKeyCount = leftKeyCount + rightKeyCount;

		// Can right keys fit within left node
		if (newLeftKeyCount <= maxKeyCount) {
			// Right node is elliminated and all entries stored in left node
			moveKeysLeft(leftNode, rightNode, rightKeyCount);
			nodeMgr.deleteNode(rightNode);
			return deleteChild(rightKey);
		}

		newLeftKeyCount = newLeftKeyCount / 2;
		if (newLeftKeyCount < leftKeyCount) {
			moveKeysRight(leftNode, rightNode, leftKeyCount - newLeftKeyCount);
		}
		else if (newLeftKeyCount > leftKeyCount) {
			moveKeysLeft(leftNode, rightNode, newLeftKeyCount - leftKeyCount);
		}
		this.keyChanged(rightKey, rightNode.getKey(0));
		return getRoot();
	}

	/**
	 * Move some (not all) of the entries from the left node into the right node.
	 * @param leftNode
	 * @param rightNode
	 * @param count
	 */
	private static void moveKeysRight(LongKeyInteriorNode leftNode, LongKeyInteriorNode rightNode,
			int count) {

		int leftKeyCount = leftNode.keyCount;
		int rightKeyCount = rightNode.keyCount;
		int leftOffset = BASE + ((leftKeyCount - count) * ENTRY_SIZE);
		int len = count * ENTRY_SIZE;
		rightNode.buffer.move(BASE, BASE + len, rightKeyCount * ENTRY_SIZE);
		rightNode.buffer.copy(BASE, leftNode.buffer, leftOffset, len);
		leftNode.setKeyCount(leftKeyCount - count);
		rightNode.setKeyCount(rightKeyCount + count);
	}

	/**
	 * Move some or all of the entries from the right node into the left node.
	 * If all keys are moved, the caller is responsible for deleting the right
	 * node.
	 * @param leftNode
	 * @param rightNode
	 * @param count
	 */
	private static void moveKeysLeft(LongKeyInteriorNode leftNode, LongKeyInteriorNode rightNode,
			int count) {

		int leftKeyCount = leftNode.keyCount;
		int rightKeyCount = rightNode.keyCount;
		int leftOffset = BASE + (leftKeyCount * ENTRY_SIZE);
		int len = count * ENTRY_SIZE;
		leftNode.buffer.copy(leftOffset, rightNode.buffer, BASE, len);
		leftNode.setKeyCount(leftKeyCount + count);
		if (count < rightKeyCount) {
			// Only need to update right node if partial move
			rightKeyCount -= count;
			rightNode.buffer.move(BASE + len, BASE, rightKeyCount * ENTRY_SIZE);
			rightNode.setKeyCount(rightKeyCount);
		}
	}

	@Override
	public void delete() throws IOException {

		// Delete all child nodes
		for (int index = 0; index < keyCount; index++) {
			nodeMgr.getLongKeyNode(getBufferId(index)).delete();
		}

		// Remove this node
		nodeMgr.deleteNode(this);
	}

	@Override
	public int[] getBufferReferences() {
		int[] ids = new int[keyCount];
		for (int i = 0; i < keyCount; i++) {
			ids[i] = getBufferId(i);
		}
		return ids;
	}

	public boolean isLeftmostKey(long key) {
		if (getIdIndex(key) == 0) {
			if (parent != null) {
				return parent.isLeftmostKey(key);
			}
			return true;
		}
		return false;
	}

	public boolean isRightmostKey(long key) {
		if (getIdIndex(key) == (keyCount - 1)) {
			if (parent != null) {
				return parent.isRightmostKey(getKey(0));
			}
			return true;
		}
		return false;
	}

}


text:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Happer.Http.Utilities;

namespace Happer.Http
{
    public class Request : IDisposable
    {
        private readonly List<HttpFile> files = new List<HttpFile>();
        private dynamic form = new DynamicDictionary();

        public Request(string method, string path, string scheme)
            : this(method, new Url { Path = path, Scheme = scheme })
        {
        }

        public Request(string method,
            Url url,
            RequestStream body = null,
            IDictionary<string, IEnumerable<string>> headers = null,
            IPEndPoint remoteEndPoint = null,
            string protocolVersion = null,
            byte[] certificate = null)
        {
            if (string.IsNullOrEmpty(method))
            {
                throw new ArgumentOutOfRangeException("method");
            }

            if (url == null)
            {
                throw new ArgumentNullException("url");
            }

            if (url.Path == null)
            {
                throw new ArgumentNullException("url.Path");
            }

            if (string.IsNullOrEmpty(url.Scheme))
            {
                throw new ArgumentOutOfRangeException("url.Scheme");
            }

            this.RemoteEndPoint = remoteEndPoint;

            this.Url = url;

            this.Method = method;

            this.Query = url.Query.AsQueryDictionary();

            this.Body = body ?? RequestStream.FromStream(new MemoryStream());

            this.Headers = new RequestHeaders(headers ?? new Dictionary<string, IEnumerable<string>>());

            this.ProtocolVersion = protocolVersion ?? string.Empty;

            if (certificate != null && certificate.Length != 0)
            {
                this.ClientCertificate = new X509Certificate2(certificate);
            }

            if (string.IsNullOrEmpty(this.Url.Path))
            {
                this.Url.Path = "/";
            }

            ParseFormData();
            RewriteMethod();
        }

        public string ProtocolVersion { get; private set; }

        public IPEndPoint RemoteEndPoint { get; private set; }

        public X509Certificate ClientCertificate { get; private set; }

        public string Method { get; private set; }

        public Url Url { get; private set; }

        public string Path
        {
            get
            {
                return this.Url.Path;
            }
        }

        public dynamic Query { get; set; }

        public RequestStream Body { get; private set; }

        public IEnumerable<HttpFile> Files
        {
            get { return this.files; }
        }

        public dynamic Form
        {
            get { return this.form; }
        }

        public RequestHeaders Headers { get; private set; }

        public void Dispose()
        {
            ((IDisposable)this.Body).Dispose();
        }

        private void ParseFormData()
        {
            if (string.IsNullOrEmpty(this.Headers.ContentType))
            {
                return;
            }

            var contentType = this.Headers["content-type"].First();
            var mimeType = contentType.Split(';').First();
            if (mimeType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
            {
                var reader = new StreamReader(this.Body);
                this.form = reader.ReadToEnd().AsQueryDictionary();
                this.Body.Position = 0;
            }

            if (!mimeType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            var boundary = Regex.Match(contentType, @"boundary=""?(?<token>[^\n\;\"" ]*)").Groups["token"].Value;
            var multipart = new HttpMultipart(this.Body, boundary);

            var formValues = new NameValueCollection(StringComparer.OrdinalIgnoreCase);

            foreach (var httpMultipartBoundary in multipart.GetBoundaries())
            {
                if (string.IsNullOrEmpty(httpMultipartBoundary.Filename))
                {
                    var reader = new StreamReader(httpMultipartBoundary.Value);
                    formValues.Add(httpMultipartBoundary.Name, reader.ReadToEnd());
                }
                else
                {
                    this.files.Add(new HttpFile(httpMultipartBoundary));
                }
            }

            foreach (var key in formValues.AllKeys.Where(key => key != null))
            {
                this.form[key] = formValues[key];
            }

            this.Body.Position = 0;
        }

        private void RewriteMethod()
        {
            if (!this.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            var overrides =
                new List<Tuple<string, string>>
                {
                    Tuple.Create("_method form input element", (string)this.Form["_method"]),
                    Tuple.Create("X-HTTP-Method-Override form input element", (string)this.Form["X-HTTP-Method-Override"]),
                    Tuple.Create("X-HTTP-Method-Override header", this.Headers["X-HTTP-Method-Override"].FirstOrDefault())
                };

            var providedOverride = overrides.Where(x => !string.IsNullOrEmpty(x.Item2));

            if (!providedOverride.Any())
            {
                return;
            }

            if (providedOverride.Count() > 1)
            {
                var overrideSources = string.Join(", ", providedOverride);
                var errorMessage = string.Format("More than one HTTP method override was provided. The provided values where: {0}", overrideSources);
                throw new InvalidOperationException(errorMessage);
            }

            this.Method = providedOverride.Single().Item2;
        }
    }
}


text:
package androidx.core.content.res;

import static android.os.Build.VERSION.SDK_INT;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.support.v4.testutils.TestUtils;
import android.util.DisplayMetrics;

import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.core.graphics.TypefaceCompat;
import androidx.core.os.BuildCompat;
import androidx.core.provider.FontsContractCompat;
import androidx.core.provider.MockFontProvider;
import androidx.core.test.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
public class ResourcesCompatTest {
    private Context mContext;
    private Resources mResources;

    @Before
    public void setup() {
        mContext = ApplicationProvider.getApplicationContext();
        mResources = mContext.getResources();
        MockFontProvider.prepareFontFiles(mContext);
    }

    @Test
    public void testGetColor() throws Throwable {
        assertEquals("Unthemed color load",
                ResourcesCompat.getColor(mResources, R.color.text_color, null),
                0xFFFF8090);

        if (SDK_INT >= 23) {
            // The following tests are only expected to pass on v23+ devices. The result of
            // calling theme-aware getColor() in pre-v23 is undefined.
            final Resources.Theme yellowTheme = mResources.newTheme();
            yellowTheme.applyStyle(R.style.YellowTheme, true);
            assertEquals("Themed yellow color load", 0xFFF0B000,
                    ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
                            yellowTheme));

            final Resources.Theme lilacTheme = mResources.newTheme();
            lilacTheme.applyStyle(R.style.LilacTheme, true);
            assertEquals("Themed lilac color load", 0xFFF080F0,
                    ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
                            lilacTheme));
        }
    }

    @Test
    public void testGetColorStateList() throws Throwable {
        final ColorStateList unthemedColorStateList =
                ResourcesCompat.getColorStateList(mResources, R.color.complex_unthemed_selector,
                        null);
        assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
                unthemedColorStateList.getDefaultColor());
        assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
                unthemedColorStateList.getColorForState(
                        new int[]{android.R.attr.state_focused}, 0));
        assertEquals("Unthemed color state list load: pressed", 0xFF6080B0,
                unthemedColorStateList.getColorForState(
                        new int[]{android.R.attr.state_pressed}, 0));

        final Resources.Theme yellowTheme = mResources.newTheme();
        yellowTheme.applyStyle(R.style.YellowTheme, true);
        final ColorStateList themedYellowColorStateList =
                ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
                        yellowTheme);
        assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
                themedYellowColorStateList.getDefaultColor());
        assertEquals("Themed yellow color state list load: focused", 0xFFF0A020,
                themedYellowColorStateList.getColorForState(
                        new int[]{android.R.attr.state_focused}, 0));
        assertEquals("Themed yellow color state list load: pressed", 0xFFE0A040,
                themedYellowColorStateList.getColorForState(
                        new int[]{android.R.attr.state_pressed}, 0));

        // reloading the same resource with a different theme should not result in the cached CSL
        // being returned; it should inflate a new one
        final Resources.Theme lilacTheme = mResources.newTheme();
        lilacTheme.applyStyle(R.style.LilacTheme, true);
        final ColorStateList themedLilacColorStateList =
                ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
                        lilacTheme);
        assertEquals("Themed lilac color state list load: default", 0xFFF080F0,
                themedLilacColorStateList.getDefaultColor());
        assertEquals("Themed lilac color state list load: focused", 0xFFF070D0,
                themedLilacColorStateList.getColorForState(
                        new int[]{android.R.attr.state_focused}, 0));
        assertEquals("Themed lilac color state list load: pressed", 0xFFE070A0,
                themedLilacColorStateList.getColorForState(
                        new int[]{android.R.attr.state_pressed}, 0));
    }

    @Test
    public void testGetDrawable() throws Throwable {
        final Drawable unthemedDrawable =
                ResourcesCompat.getDrawable(mResources, R.drawable.test_drawable_red, null);
        TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
                unthemedDrawable, mResources.getColor(R.color.test_red));

        if (SDK_INT >= 23) {
            // The following tests are only expected to pass on v23+ devices. The result of
            // calling theme-aware getDrawable() in pre-v23 is undefined.
            final Resources.Theme yellowTheme = mResources.newTheme();
            yellowTheme.applyStyle(R.style.YellowTheme, true);
            final Drawable themedYellowDrawable =
                    ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable,
                            yellowTheme);
            TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
                    themedYellowDrawable, 0xFFF0B000);

            final Resources.Theme lilacTheme = mResources.newTheme();
            lilacTheme.applyStyle(R.style.LilacTheme, true);
            final Drawable themedLilacDrawable =
                    ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable, lilacTheme);
            TestUtils.assertAllPixelsOfColor("Themed lilac drawable load",
                    themedLilacDrawable, 0xFFF080F0);
        }
    }

    @Test
    public void testGetDrawableForDensityUnthemed() throws Throwable {
        // Density-aware drawable loading for now only works on raw bitmap drawables.

        // Different variants of density_aware_drawable are set up in the following way:
        //    mdpi - 12x12 px which is 12x12 dip
        //    hdpi - 21x21 px which is 14x14 dip
        //   xhdpi - 32x32 px which is 16x16 dip
        //  xxhdpi - 54x54 px which is 18x18 dip
        // The tests below (on v15+ devices) are checking that an unthemed density-aware
        // loading of raw bitmap drawables returns a drawable with matching intrinsic
        // dimensions.

        final Drawable unthemedDrawableForMediumDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                        DisplayMetrics.DENSITY_MEDIUM, null);
        // For pre-v15 devices we should get a drawable that corresponds to the density of the
        // current device. For v15+ devices we should get a drawable that corresponds to the
        // density requested in the API call.
        final int expectedSizeForMediumDensity = (SDK_INT < 15) ?
                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
        assertEquals("Unthemed density-aware drawable load: medium width",
                expectedSizeForMediumDensity, unthemedDrawableForMediumDensity.getIntrinsicWidth());
        assertEquals("Unthemed density-aware drawable load: medium height",
                expectedSizeForMediumDensity,
                unthemedDrawableForMediumDensity.getIntrinsicHeight());

        final Drawable unthemedDrawableForHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                        DisplayMetrics.DENSITY_HIGH, null);
        // For pre-v15 devices we should get a drawable that corresponds to the density of the
        // current device. For v15+ devices we should get a drawable that corresponds to the
        // density requested in the API call.
        final int expectedSizeForHighDensity = (SDK_INT < 15) ?
                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
        assertEquals("Unthemed density-aware drawable load: high width",
                expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicWidth());
        assertEquals("Unthemed density-aware drawable load: high height",
                expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicHeight());

        final Drawable unthemedDrawableForXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                        DisplayMetrics.DENSITY_XHIGH, null);
        // For pre-v15 devices we should get a drawable that corresponds to the density of the
        // current device. For v15+ devices we should get a drawable that corresponds to the
        // density requested in the API call.
        final int expectedSizeForXHighDensity = (SDK_INT < 15) ?
                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
        assertEquals("Unthemed density-aware drawable load: xhigh width",
                expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicWidth());
        assertEquals("Unthemed density-aware drawable load: xhigh height",
                expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicHeight());

        final Drawable unthemedDrawableForXXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                        DisplayMetrics.DENSITY_XXHIGH, null);
        // For pre-v15 devices we should get a drawable that corresponds to the density of the
        // current device. For v15+ devices we should get a drawable that corresponds to the
        // density requested in the API call.
        final int expectedSizeForXXHighDensity = (SDK_INT < 15) ?
                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
        assertEquals("Unthemed density-aware drawable load: xxhigh width",
                expectedSizeForXXHighDensity, unthemedDrawableForXXHighDensity.getIntrinsicWidth());
        assertEquals("Unthemed density-aware drawable load: xxhigh height",
                expectedSizeForXXHighDensity,
                unthemedDrawableForXXHighDensity.getIntrinsicHeight());
    }

    @Test
    public void testGetDrawableForDensityThemed() throws Throwable {
        if (SDK_INT < 21) {
            // The following tests are only expected to pass on v21+ devices. The result of
            // calling theme-aware getDrawableForDensity() in pre-v21 is undefined.
            return;
        }

        // Density- and theme-aware drawable loading for now only works partially. This test
        // checks only for theming of a tinted bitmap XML drawable, but not correct scaling.

        // Set up the two test themes, yellow and lilac.
        final Resources.Theme yellowTheme = mResources.newTheme();
        yellowTheme.applyStyle(R.style.YellowTheme, true);

        final Resources.Theme lilacTheme = mResources.newTheme();
        lilacTheme.applyStyle(R.style.LilacTheme, true);

        Drawable themedYellowDrawableForMediumDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_MEDIUM, yellowTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : medium color",
                themedYellowDrawableForMediumDensity, 0xFFF0B000);

        Drawable themedLilacDrawableForMediumDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_MEDIUM, lilacTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : medium color",
                themedLilacDrawableForMediumDensity, 0xFFF080F0);

        Drawable themedYellowDrawableForHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_HIGH, yellowTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : high color",
                themedYellowDrawableForHighDensity, 0xFFF0B000);

        Drawable themedLilacDrawableForHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_HIGH, lilacTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : high color",
                themedLilacDrawableForHighDensity, 0xFFF080F0);

        Drawable themedYellowDrawableForXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_XHIGH, yellowTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xhigh color",
                themedYellowDrawableForXHighDensity, 0xFFF0B000);

        Drawable themedLilacDrawableForXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_XHIGH, lilacTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xhigh color",
                themedLilacDrawableForXHighDensity, 0xFFF080F0);

        Drawable themedYellowDrawableForXXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_XXHIGH, yellowTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xxhigh color",
                themedYellowDrawableForXXHighDensity, 0xFFF0B000);

        Drawable themedLilacDrawableForXXHighDensity =
                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                        DisplayMetrics.DENSITY_XXHIGH, lilacTheme);
        // We should get a drawable that corresponds to the theme requested in the API call.
        TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xxhigh color",
                themedLilacDrawableForXXHighDensity, 0xFFF080F0);
    }

    @Test(expected = Resources.NotFoundException.class)
    public void testGetFont_invalidResourceId() {
        ResourcesCompat.getFont(mContext, -1);
    }

    @Test
    public void testGetFont_fontFile_sync() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont);

        assertNotNull(font);
        assertNotSame(Typeface.DEFAULT, font);
    }

    @Test
    public void testGetFont_adjustDisplayStyle() {
        Typeface tf = ResourcesCompat.getFont(mContext, R.font.thin_italic);

        assertNotNull(tf);
        if (SDK_INT >= 28) {
            assertEquals(100, tf.getWeight());
        }
        assertEquals(Typeface.ITALIC, tf.getStyle());
    }

    @Test
    public void testGetFont_fontFile_cached() {
        TypefaceCompat.clearCache();
        assertNull(ResourcesCompat.getCachedFont(mContext, R.font.samplefont));
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont);
        Typeface cachedFont = ResourcesCompat.getCachedFont(mContext, R.font.samplefont);
        assertNotNull(cachedFont);
        assertSame(font, cachedFont);
    }

    private static final class FontCallback extends ResourcesCompat.FontCallback {
        private final CountDownLatch mLatch;
        Typeface mTypeface;

        FontCallback(CountDownLatch latch) {
            mLatch = latch;
        }

        @Override
        public void onFontRetrieved(@NonNull Typeface typeface) {
            mTypeface = typeface;
            mLatch.countDown();
        }

        @Override
        public void onFontRetrievalFailed(int reason) {
            mLatch.countDown();
        }
    }

    @Test
    public void testGetFont_fontFile_async() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);
        FontsContractCompat.resetTypefaceCache();

        ResourcesCompat.getFont(mContext, R.font.samplefont, callback, null);

        assertTrue(latch.await(5L, TimeUnit.SECONDS));

        assertNotNull(callback.mTypeface);
        assertNotSame(Typeface.DEFAULT, callback.mTypeface);
    }

    @Test
    public void testGetFont_xmlFile_sync() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);

        assertNotNull(font);
        assertNotSame(Typeface.DEFAULT, font);
    }

    @Test
    @SdkSuppress(minSdkVersion = 29)
    public void testGetFont_xmlFile_sync_29_hasRightWeight() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont_medium);

        assertNotNull(font);
        assertEquals(500, font.getWeight());
    }

    @Test
    public void testGetFont_xmlFile_async() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);

        ResourcesCompat.getFont(mContext, R.font.samplexmlfont, callback, null);

        assertTrue(latch.await(5L, TimeUnit.SECONDS));

        assertNotNull(callback.mTypeface);
        assertNotSame(Typeface.DEFAULT, callback.mTypeface);
    }

    @Test
    @SdkSuppress(minSdkVersion = 29)
    public void testGetFont_xmlFile_async_29_hasRightWeight() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);

        ResourcesCompat.getFont(mContext, R.font.samplexmlfont_medium, callback, null);

        assertTrue(latch.await(5L, TimeUnit.SECONDS));

        assertNotNull(callback.mTypeface);
        assertEquals(500, callback.mTypeface.getWeight());
    }

    @Test(expected = Resources.NotFoundException.class)
    public void testGetFont_brokenFont() {
        ResourcesCompat.getFont(mContext, R.font.invalid_font);
    }

    @Test
    public void testGetFont_xmlProviderFile_sync() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmldownloadedfont);

        assertNotNull(font);
        assertNotSame(Typeface.DEFAULT, font);
    }

    @Test
    public void testGetFont_xmlProviderFile_async() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final FontCallback callback = new FontCallback(latch);

        // Font provider non-blocking requests post on the calling thread so can't run on
        // the test thread as it doesn't have a Looper.
        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                ResourcesCompat.getFont(mContext, R.font.samplexmldownloadedfont, callback, null);
            }
        });

        assertTrue(latch.await(5L, TimeUnit.SECONDS));

        assertNotNull(callback.mTypeface);
        assertNotSame(Typeface.DEFAULT, callback.mTypeface);
    }

    @Test
    public void testGetFont_invalidXmlFile() {
        try {
            assertNull(
                    ResourcesCompat.getFont(mContext, R.font.invalid_xmlfamily));
        } catch (Resources.NotFoundException e) {
            // pass
        }

        try {
            assertNull(ResourcesCompat.getFont(mContext, R.font.invalid_xmlempty));
        } catch (Resources.NotFoundException e) {
            // pass
        }
    }

    @Test
    public void testGetFont_fontFileIsCached() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont);
        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplefont);

        assertSame(font, font2);
    }

    @Test
    public void testGetFont_xmlFileIsCached() {
        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);
        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);

        assertSame(font, font2);
    }

    @FlakyTest(bugId = 190534138)
    @Test
    public void testSystemFontFamilyReturnsSystemFont() {
        Typeface typeface = ResourcesCompat.getFont(mContext, R.font.samplexmldownloadedfont);
        assertNotNull(typeface);
        assertEquals(typeface,  Typeface.create("serif", Typeface.NORMAL));
    }

    @Test
    public void getFloatForFloat() {
        float value = ResourcesCompat.getFloat(mResources, R.dimen.twelve_point_five);
        assertEquals(12.5f, value, 0.01f);
    }

    @Test
    public void getFloatForNotAFloatThrows() {
        try {
            ResourcesCompat.getFloat(mResources, android.R.string.yes);
            fail();
        } catch (Resources.NotFoundException e) {
            assertEquals("Resource ID #0x1040013 type #0x3 is not valid", e.getMessage());
        }
    }

    @Test
    public void getFloatForMissingResourceThrows() {
        try {
            ResourcesCompat.getFloat(mResources, 42);
            fail();
        } catch (Resources.NotFoundException e) {
            assertEquals("Resource ID #0x2a", e.getMessage());
        }
    }

    @Test
    public void testMutateTransitionDrawable() {
        Drawable drawable = ResourcesCompat.getDrawable(mResources,
                R.drawable.test_transition_drawable, null);
        assertTrue(drawable instanceof TransitionDrawable);

        Drawable mutated = drawable.mutate();
        assertTrue(drawable instanceof TransitionDrawable);
        assertTrue(mutated instanceof TransitionDrawable);
    }

    @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
    @Test
    public void testClearCachesForTheme() {
        Resources.Theme theme = mResources.newTheme();
        ColorStateList csl = ResourcesCompat.getColorStateList(
                mResources, R.color.color_state_list, theme);

        // Modify the contents of the theme.
        theme.applyStyle(android.R.style.Theme_Material, true);
        ColorStateList csl2 = ResourcesCompat.getColorStateList(
                mResources, R.color.color_state_list, theme);

        if (!BuildCompat.isAtLeastT()) {
            // Validate the failure case that's being worked around.
            assertEquals(csl, csl2);
        } else {
            // Theme.hashCode() was implemented in T, so the workaround is not needed.
            assertNotEquals(csl, csl2);
        }

        ResourcesCompat.clearCachesForTheme(theme);

        // Validate the workaround yields a correct result for all platforms.
        ColorStateList csl3 = ResourcesCompat.getColorStateList(
                mResources, R.color.color_state_list, theme);
        assertNotEquals(csl, csl3);
    }
}


text:
A reverse proxy to untie the knot between frontend and backend developers

[<img src="https://travis-ci.org/candrholdings/detour.svg?branch=master" />](https://travis-ci.org/candrholdings/detour) [![Join the chat at https://gitter.im/candrholdings/detour](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/candrholdings/detour?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## Problems
Frontend developers cannot focus on their development because

* Backend deployment is complicated and take up lot of resources on their box
* New features depends on backend API readiness
  * Mocking is okay but changing spec makes mock update time consuming
* Need to tackle CORS issue during tight development schedule

## Scenarios
* Decouple frontend and backend development team
  * Join web server and API servers together as if they are from a single server
  * Few frontend developers can use a shared backend server, no need to install database in their own box to run the whole site
  * Don't wait until backend API is ready, mock a response JSON/XML and continue web development
  * Built-in LiveReload server for quick consumption
* Mock changes on big production site
  * Mock as you need, host a smaller staging site without downloading everything from production
  * Support server side includes
* Simple URL rewrite

## Install
Run `npm install detour-proxy -g` to install detour in your box. For every site,

* Create a [`detour-config.json`](detour-config.json) (sample configuration below)
* Run `detour` to start the server
  * For custom config path and port, run `detour path-to-config-file.json 1337`

## Sample configuration
```js
{
    "logLevel": "follow",
    "mappings": [{
        "from": "/",
        "to": "html/",
        "skipOn404": true
    }, {
        "from": "/api/",
        "to": "http://api.example.com/"
    }]
}
```

`logLevel` could be `error`, `warning`, `info`, `skip`, `forward`, `follow`. Detour use [`winston`](https://github.com/winstonjs/winston/).

`mappings` defines how request could be remapped. Mappings can be overlapping and they are processed from top to bottom (first win will stop other rules).

In the sample configuration, the first mapping will rewrite `/` (i.e. all requests to the site) to local directory `html/`. For example, request to `/helloworld.html` will be served with local file `html/helloworld.html`, relative to the config file.

`skipOn404` indicate that if the requested file is not found, it will skip this mapping and evaluate the next one. Otherwise, detour will return with 404.

You can specify both file path or http:// path to the `to` argument.

The second mapping will rewrite `/api/` to http://api.example.com/. For example, requests to `/api/helloworld` will be forwarded on behalf to http://api.example.com/helloworld/. Most request headers will be retained. Some are discarded, e.g. `accept-encoding`.

Currently, we do not add headers (e.g. `x-forwarded-for`, `x-forwarded-proto`) to the forwarded request.

### Default port
By default, detour use a random port number 7000-7999. To use a custom port number, you can specify it in the command-line. Type `detour path-to-config-file.json 1337` to run detour on port 1337 with configuration file `path-to-config-file.json`.

### Default document
For file-based destination, detour adopted `index.html` as the name of "default document". If the request does not contain a filename (end with slash) and the mapping destination is local file system, detour will add `index.html` to the request URL.

### Server side includes
Detour follow server side includes (or SSI). The SSI path will also be evaluated against the mappings defined in `detour-config.json`. To aid development, the SSI-ed output will have extra comment `<!-- BEGIN SSI /include/topnav.html -->` for easier debugging SSI issues.

### LiveReload support
Detour built-in [LiveReload server](http://livereload.com/). When detour is up, it will watch all mappings that point to local file system. Changes detected will be broadcasted to LiveReload plugin immediately. Instead of using unreliable [`fs.watch`](https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener), we use a more reliable approach by crawling every file stats every 2 seconds. We still use [`fs.watch`](https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener) for auxiliary purpose.

### Changelog
* 0.0.25 (2016-04-20): Log LiveReload events
* 0.0.24 (2016-03-30): Use [chokidar](https://npmjs.com/package/chokidar) for file watcher

### Issues
If you encounter any issues, please file it on [GitHub](../../issues). To speed up our investigation, please file with a minimal repro steps.

### Feature requests
If you want to recommend a feature, please file it on [issues](../../issues) or chat with us on [Gitter](https://gitter.im/candrholdings/detour). Please let us know rationale behind your request and we will be more than happy to design and implement the feature for you.


text:
from django.conf.urls import patterns, include, url

urlpatterns = patterns('',
    url(r'^$', 'views.home', name='home'),
    url(r'^identity/', include('identity.urls')),
)


text:
<a [class]="getComponentClasses()" [routerLink]="routerLink">
  <ng-content></ng-content>
</a>


text:
using System;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.PhantomJS;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.IE;

namespace Protractor.Samples.Basic
{
    [TestFixture]
    public class BasicTests
    {
        private IWebDriver driver;

        [SetUp]
        public void SetUp()
        {
            // Using NuGet Package 'PhantomJS'
            driver = new PhantomJSDriver();

            // Using NuGet Package 'WebDriver.ChromeDriver.win32'
            //driver = new ChromeDriver();

            // Using NuGet Package 'WebDriver.IEDriverServer.win32'
            //var options = new InternetExplorerOptions() { IntroduceInstabilityByIgnoringProtectedModeSettings = true };
            //driver = new InternetExplorerDriver(options);

            driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromSeconds(5));
        }

        [TearDown]
        public void TearDown()
        {
            driver.Quit();
        }

        [Test]
        public void ShouldGreetUsingBinding()
        {
            IWebDriver ngDriver = new NgWebDriver(driver);
            ngDriver.Navigate().GoToUrl("http://www.angularjs.org");
            ngDriver.FindElement(NgBy.Model("yourName")).SendKeys("Julie");
            Assert.AreEqual("Hello Julie!", ngDriver.FindElement(NgBy.Binding("yourName")).Text);
        }

        [Test]
        public void ShouldListTodos()
        {
            var ngDriver = new NgWebDriver(driver);
            ngDriver.Navigate().GoToUrl("http://www.angularjs.org");
            var elements = ngDriver.FindElements(NgBy.Repeater("todo in todoList.todos"));
            Assert.AreEqual("build an angular app", elements[1].Text);
            Assert.AreEqual(false, elements[1].Evaluate("todo.done"));
        }
    }
}